WIP mobile header + developer dropdown

This commit is contained in:
Fred Carlsen
2018-12-10 11:19:20 +01:00
parent 8dd738629a
commit c995586063
3 changed files with 414 additions and 29 deletions

View File

@@ -0,0 +1,159 @@
import { ALink, Link } from '@0x/react-shared';
import * as _ from 'lodash';
import * as React from 'react';
import { colors } from 'ts/style/colors';
import { Container } from 'ts/components/ui/container';
import { DropDown } from 'ts/components/ui/drop_down';
import { Heading, Paragraph } from 'ts/@next/components/text';
import { Deco, Key, WebsitePaths } from 'ts/types';
import { constants } from 'ts/utils/constants';
import { Translate } from 'ts/utils/translate';
const gettingStartedKeyToLinkInfo1: ALink[] = [
{
title: Key.BuildARelayer,
to: `${WebsitePaths.Wiki}#Build-A-Relayer`,
},
{
title: Key.OrderBasics,
to: `${WebsitePaths.Wiki}#Create,-Validate,-Fill-Order`,
},
];
const gettingStartedKeyToLinkInfo2: ALink[] = [
{
title: Key.DevelopOnEthereum,
to: `${WebsitePaths.Wiki}#Ethereum-Development`,
},
{
title: Key.UseNetworkedLiquidity,
to: `${WebsitePaths.Wiki}#Find,-Submit,-Fill-Order-From-Relayer`,
},
];
const popularDocsToLinkInfos: ALink[] = [
{
title: Key.ZeroExJs,
to: WebsitePaths.ZeroExJs,
},
{
title: Key.Connect,
to: WebsitePaths.Connect,
},
{
title: Key.SmartContract,
to: WebsitePaths.SmartContracts,
},
];
const usefulLinksToLinkInfo: ALink[] = [
{
title: Key.Wiki,
to: WebsitePaths.Wiki,
},
{
title: Key.Github,
to: constants.URL_GITHUB_ORG,
shouldOpenInNewTab: true,
},
{
title: Key.Whitepaper,
to: WebsitePaths.Whitepaper,
shouldOpenInNewTab: true,
},
];
interface DevelopersDropDownProps {
location: Location;
}
interface DevelopersDropDownState {}
export class DevelopersDropDown extends React.Component<DevelopersDropDownProps, DevelopersDropDownState> {
public render(): React.ReactNode {
const activeNode = (
<Paragraph isNoMargin={true}>
Developers
</Paragraph>
);
return (
<DropDown
activeNode={activeNode}
popoverContent={this._renderDropdownMenu()}
anchorOrigin={{ horizontal: 'left', vertical: 'bottom' }}
targetOrigin={{ horizontal: 'left', vertical: 'top' }}
popoverStyle={{ borderRadius: 0, width: 420, height: 377, marginTop: 0 }}
/>
);
}
private _renderDropdownMenu(): React.ReactNode {
const sectionPadding = '26px';
const dropdownMenu = (
<Container>
<Container className="flex" padding={sectionPadding}>
<Container paddingRight="45px">
{this._renderLinkSection(gettingStartedKeyToLinkInfo1, 'Getting started')}
</Container>
<Container>{this._renderLinkSection(gettingStartedKeyToLinkInfo2)}</Container>
</Container>
<Container width="100%" height="1px" backgroundColor={colors.grey300} />
<Container className="flex" padding={sectionPadding}>
<Container paddingRight="62px">
<Container>{this._renderLinkSection(popularDocsToLinkInfos, 'Popular docs')}</Container>
</Container>
<Container>
<Container>{this._renderLinkSection(usefulLinksToLinkInfo, 'Useful links')}</Container>
</Container>
</Container>
<Link to={WebsitePaths.Docs} fontColor={colors.brandLight}>
<Container
padding="0.9rem"
backgroundColor={colors.white}
borderBottomLeftRadius={4}
borderBottomRightRadius={4}
>
<Paragraph color={colors.brandLight} isCentered={true} isNoMargin={true}>
View all documentation
</Paragraph>
</Container>
</Link>
</Container>
);
return dropdownMenu;
}
private _renderLinkSection(links: ALink[], title: string = ''): React.ReactNode {
const numLinks = links.length;
let i = 0;
const renderLinks = _.map(links, (link: ALink) => {
const isWikiLink = _.startsWith(link.to, WebsitePaths.Wiki) && _.includes(link.to, '#');
const isOnWiki = this.props.location.pathname === WebsitePaths.Wiki;
let to = link.to;
if (isWikiLink && isOnWiki) {
to = `${link.to.split('#')[1]}`;
}
i++;
const isLast = i === numLinks;
// const linkText = this.props.translate.get(link.title as Key, Deco.Cap);
const linkText = link.title;
return (
<Container className={`pr1 pt1 ${!isLast && 'pb1'}`} key={`dev-dropdown-link-${link.title}`}>
<Link to={to} shouldOpenInNewTab={!!link.shouldOpenInNewTab}>
<Paragraph size="small" color={colors.brandDark} isNoMargin={true}>
{linkText}
</Paragraph>
</Link>
</Container>
);
});
return (
<Container>
<Container height="33px">
{!_.isEmpty(title) && (
<Heading asElement="h3" size="small">
{title}
</Heading>
)}
</Container>
{renderLinks}
</Container>
);
}
}

View File

@@ -0,0 +1,68 @@
import * as React from 'react';
import styled from 'styled-components';
interface Props {
isOpen: boolean;
onClick?: () => void;
}
export const Hamburger: React.FunctionComponent<Props> = (props: Props) => {
return (
<StyledHamburger isOpen={props.isOpen} onClick={props.onClick}>
<span />
<span />
<span />
</StyledHamburger>
);
};
const StyledHamburger = styled.button<Props>`
background: none;
border: 0;
width: 22px;
height: 16px;
position: relative;
z-index: 2;
padding: 0;
outline: none;
user-select: none;
@media (min-width: 768px) {
display: none;
}
span {
display: block;
background-color: #fff;
width: 100%;
height: 2px;
margin-bottom: 5px;
transform-origin: 4px 0px;
transition: transform 0.5s cubic-bezier(0.77,0.2,0.05,1.0),
background-color 0.5s cubic-bezier(0.77,0.2,0.05,1.0),
opacity 0.55s ease;
&:first-child {
//transform-origin: 0% 0%;
}
&:last-child {
//transform-origin: 0% 100%;
}
${props => props.isOpen && `
opacity: 1;
transform: rotate(45deg) translate(0, 1px);
background-color: #fff;
&:nth-child(2) {
opacity: 0;
transform: rotate(0deg) scale(0.2, 0.2);
}
&:last-child {
transform: rotate(-45deg) translate(1px, -4px);
}
`}
}
`;

View File

@@ -3,48 +3,206 @@ import * as React from 'react';
import { Link as ReactRouterLink } from 'react-router-dom';
import styled from 'styled-components';
import { colors } from 'ts/style/colors';
import { Button, ButtonWrap, Link } from 'ts/@next/components/button';
import { DevelopersDropDown } from 'ts/@next/components/dropdowns/developers_drop_down';
import { Hamburger } from 'ts/@next/components/hamburger';
import { Section, Wrap } from 'ts/@next/components/layout';
import { Logo } from 'ts/@next/components/logo';
import { Paragraph } from 'ts/@next/components/text';
interface HeaderProps {
isOpen: boolean;
location?: Location;
}
const links = [
{ url: '/next/why', text: 'Why 0x' },
{ url: '/next/0x-instant', text: 'Products' },
{ url: '#', text: 'Developers' },
{ url: '/next/about/mission', text: 'About' },
{ url: '#', text: 'Blog' },
interface HeaderState {
isOpen: boolean;
}
interface HeaderState {
isOpen: boolean;
}
interface NavItem {
url?: string;
id?: string;
text?: string;
}
const mobileProductLinks = [
{ url: '/next/0x-instant', text: '0x Instant' },
{ url: '/next/launch-kit', text: '0x Launch Kit' },
];
export const Header: React.StatelessComponent<HeaderProps> = ({}) => (
<StyledHeader>
<HeaderWrap>
<ReactRouterLink to="/next">
<Logo/>
</ReactRouterLink>
const navItems: NavItem[] = [
{ id: 'why', url: '/next/why', text: 'Why 0x' },
{ id: 'products', url: '/next/0x-instant', text: 'Products' },
{ id: 'developers', url: '#', text: 'Developers' },
{ id: 'about', url: '/next/about/mission', text: 'About' },
{ id: 'blog', url: '#', text: 'Blog' },
];
<ButtonWrap>
{_.map(links, (link, index) => (
<Link
key={`hb-${index}`}
href={link.url}
isTransparent={true}
isNoBorder={true}
>
{link.text}
</Link>
))}
</ButtonWrap>
export class Header extends React.Component<HeaderProps, HeaderState> {
constructor(props: HeaderProps) {
super(props);
this.state = {
isOpen: false,
};
}
public render(): React.ReactNode {
return (
<StyledHeader isOpen={this.state.isOpen}>
<HeaderWrap>
<ReactRouterLink to="/next">
<Logo/>
</ReactRouterLink>
<Hamburger isOpen={this.state.isOpen} onClick={this._onMenuButtonClick.bind(this)}/>
<Nav>
<MobileProductLinksWrap>
<Paragraph isNoMargin={true} isMuted={0.5} size="small">Products</Paragraph>
{_.map(mobileProductLinks, (link, index) => (
<StyledLink
key={`productlink-${index}`}
href={link.url}
isTransparent={true}
isNoBorder={true}
>
{link.text}
</StyledLink>
))}
</MobileProductLinksWrap>
<StyledButtonWrap>
{_.map(navItems, (link, index) => this._getNavItem(link, index))}
</StyledButtonWrap>
</Nav>
<TradeButton href="#">Trade on 0x</TradeButton>
</HeaderWrap>
</StyledHeader>
);
}
private _onMenuButtonClick(): void {
this.setState({
isOpen: !this.state.isOpen,
});
}
private _getNavItem(link: NavItem, index: number): React.ReactNode {
if (link.id === 'developers') {
return (
<DevelopersDropDown
location={window.location}
/>
);
}
<Button href="#">Trade on 0x</Button>
</HeaderWrap>
</StyledHeader>
);
return (
<StyledLink
key={`header-nav-item-${index}`}
href={link.url}
isTransparent={true}
isNoBorder={true}
>
{link.text}
</StyledLink>
)
}
}
const StyledHeader = styled(Section.withComponent('header'))<HeaderProps>`
@media (max-width: 768px) {
overflow: hidden;
min-height: ${props => props.isOpen ? '385px' : '70px'};
position: relative;
transition: min-height 0.25s ease-in-out;
:root & {
padding: 20px 20px 0 !important;
}
}
`;
const StyledHeader = Section.withComponent('header');
const HeaderWrap = styled(Wrap)`
justify-content: space-between;
align-items: center;
@media (max-width: 768px) {
padding-top: 0;
display: flex;
padding-bottom: 0;
}
`;
const StyledButtonWrap = styled(ButtonWrap)`
display: flex;
@media (max-width: 768px) {
background-color: #022924;
display: flex;
flex-wrap: wrap;
padding: 20px 20px;
a {
text-align: left;
padding-left: 0;
}
}
button + button,
a + a,
a + button,
button + a {
margin-left: 0;
@media (min-width: 768px) {
margin-left: 10px;
}
}
`;
const MobileProductLinksWrap = styled(StyledButtonWrap)`
display: none;
@media (max-width: 768px) {
display: block;
background-color: transparent;
flex-direction: column;
a {
display: block;
width: 100%;
}
}
`;
const StyledLink = styled(Link)`
width: 50%;
text-align: left;
@media (max-width: 768px) {
}
@media (min-width: 768px) {
width: auto;
text-align: center;
}
`;
const Nav = styled.div`
@media (max-width: 768px) {
background-color: ${colors.brandDark};
position: absolute;
top: 0;
left: 0;
right: 0;
padding-top: 65px;
}
@media (min-width: 768px) {
width: auto;
text-align: center;
}
`;
const TradeButton = styled(Button)`
@media (max-width: 768px) {
display: none;
}
`;