changed to popular
This commit is contained in:
parent
f72918362d
commit
c642cd6fed
BIN
packages/website/public/.DS_Store
vendored
BIN
packages/website/public/.DS_Store
vendored
Binary file not shown.
@ -17,6 +17,7 @@
|
||||
<link rel="stylesheet" href="/css/basscss_responsive_padding.css" />
|
||||
<link rel="stylesheet" href="/css/basscss_responsive_margin.css" />
|
||||
<link rel="stylesheet" href="/css/basscss_responsive_type_scale.css" />
|
||||
<script src="https://instant.0x.org/instant.js"></script>
|
||||
</head>
|
||||
|
||||
<body style="margin: 0px; min-width: 355px;">
|
||||
|
@ -18,6 +18,7 @@ export enum ModalContactType {
|
||||
General = 'GENERAL',
|
||||
MarketMaker = 'MARKET_MAKER',
|
||||
Credits = 'CREDITS',
|
||||
Explore = 'EXPLORE',
|
||||
}
|
||||
|
||||
interface ServiceOptionMetadata {
|
||||
@ -142,6 +143,8 @@ export class ModalContact extends React.Component<Props> {
|
||||
return this._renderMarketMakerFormContent(errors);
|
||||
case ModalContactType.Credits:
|
||||
return this._renderCreditsFormContent(errors);
|
||||
case ModalContactType.Explore:
|
||||
return this._renderExploreFormContent(errors);
|
||||
case ModalContactType.General:
|
||||
default:
|
||||
return this._renderGeneralFormContent(errors);
|
||||
@ -218,6 +221,99 @@ export class ModalContact extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
private _renderExploreFormContent(errors: ErrorProps): React.ReactNode {
|
||||
return (
|
||||
<>
|
||||
<Paragraph isMuted={true} color={colors.textDarkPrimary}>
|
||||
If you’re working on an awesome 0x project, we would love to share it on our explore page. Fill out the form
|
||||
so we can connect you with the right person to help you get started.
|
||||
</Paragraph>
|
||||
<InputRow>
|
||||
<Input
|
||||
name="name"
|
||||
label="Your name"
|
||||
type="text"
|
||||
width={InputWidth.Half}
|
||||
ref={this.nameRef}
|
||||
required={true}
|
||||
errors={errors}
|
||||
/>
|
||||
<Input
|
||||
name="email"
|
||||
label="Your email"
|
||||
type="email"
|
||||
ref={this.emailRef}
|
||||
required={true}
|
||||
errors={errors}
|
||||
width={InputWidth.Half}
|
||||
/>
|
||||
</InputRow>
|
||||
<InputRow>
|
||||
<Input
|
||||
name="companyOrProject"
|
||||
label="Name of your project / company"
|
||||
type="text"
|
||||
ref={this.companyProjectRef}
|
||||
required={true}
|
||||
errors={errors}
|
||||
/>
|
||||
</InputRow>
|
||||
<InputRow>
|
||||
<Input
|
||||
name="comments"
|
||||
label="Description of your project / company"
|
||||
type="textarea"
|
||||
ref={this.commentsRef}
|
||||
required={true}
|
||||
errors={errors}
|
||||
/>
|
||||
</InputRow>
|
||||
<InputRow>
|
||||
<Input
|
||||
name="link"
|
||||
label="Project / Company link"
|
||||
type="text"
|
||||
ref={this.commentsRef}
|
||||
required={true}
|
||||
errors={errors}
|
||||
/>
|
||||
</InputRow>
|
||||
<Paragraph isMuted={true} color={colors.textDarkPrimary}>
|
||||
Details for 0x Explore page:
|
||||
</Paragraph>
|
||||
<InputRow>
|
||||
<Input
|
||||
name="color"
|
||||
label="Theme Color (in hex)"
|
||||
type="text"
|
||||
ref={this.commentsRef}
|
||||
required={true}
|
||||
errors={errors}
|
||||
/>
|
||||
</InputRow>
|
||||
<InputRow>
|
||||
<OptionSelector
|
||||
isFlex={true}
|
||||
name="instant"
|
||||
label="Does your project support instant?"
|
||||
errors={errors}
|
||||
>
|
||||
{[{label: 'Yes', name: 'yes'}, {label: 'No', name: 'no'}].map((metadata: ServiceOptionMetadata) => {
|
||||
return (
|
||||
<CheckBoxInput
|
||||
onClick={this._handleCheckBoxInput.bind(this, metadata.name)}
|
||||
key={`checkbox-${metadata.name}`}
|
||||
isSelected={_.includes(this.state.creditLeadsServices, metadata.name)}
|
||||
label={metadata.label}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</OptionSelector>
|
||||
</InputRow>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private _renderCreditsFormContent(errors: ErrorProps): React.ReactNode {
|
||||
return (
|
||||
<>
|
||||
|
20
packages/website/ts/components/ui/switch.tsx
Normal file
20
packages/website/ts/components/ui/switch.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import * as React from 'react';
|
||||
import styled, { withTheme } from 'styled-components';
|
||||
|
||||
import { Heading } from 'ts/components/text';
|
||||
|
||||
const SwitchWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.5rem 0;
|
||||
`;
|
||||
|
||||
export interface SwitchProps {
|
||||
label: string;
|
||||
}
|
||||
|
||||
export const Switch = (props: SwitchProps) => {
|
||||
return <SwitchWrapper>
|
||||
<Heading isNoMargin={true} asElement="h3" size={'small'}>{props.label}</Heading>
|
||||
</SwitchWrapper>;
|
||||
};
|
1
packages/website/ts/globals.d.ts
vendored
1
packages/website/ts/globals.d.ts
vendored
@ -10,6 +10,7 @@ declare module 'react-anchor-link-smooth-scroll';
|
||||
declare module 'react-responsive';
|
||||
declare module 'react-scrollable-anchor';
|
||||
declare module 'react-headroom';
|
||||
declare module 'zeroExInstant';
|
||||
|
||||
declare module '*.json' {
|
||||
const json: any;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import * as zeroExInstant from 'zeroExInstant';
|
||||
|
||||
import { Banner } from 'ts/components/banner';
|
||||
import { DocumentTitle } from 'ts/components/document_title';
|
||||
@ -10,11 +11,13 @@ import { Section } from 'ts/components/newLayout';
|
||||
import { SiteWrap } from 'ts/components/siteWrap';
|
||||
import { Heading } from 'ts/components/text';
|
||||
import { Input as SearchInput } from 'ts/components/ui/search_textfield';
|
||||
import { ExploreGrid } from 'ts/pages/explore/explore_grid';
|
||||
import { ExploreGrid, ExploreGridListTile, ExploreGridListTileVisibility, ExploreGridListTileWidth } 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 { colors } from 'ts/style/colors';
|
||||
import { ExploreEntry, ExploreEntryVisibility, ExploreFilterMetadata, ExploreFilterType, RicherExploreEntry } from 'ts/types';
|
||||
import { ExploreEntry, ExploreEntryInstantMetadata, RicherExploreEntry } from 'ts/types';
|
||||
import { documentConstants } from 'ts/utils/document_meta_constants';
|
||||
import { ExploreSettingsDropdown } from 'ts/pages/explore/explore_dropdown';
|
||||
|
||||
export interface ExploreProps {}
|
||||
|
||||
@ -26,9 +29,6 @@ const PROJECTS: { [s: string]: ExploreEntry } = {
|
||||
theme_color: '#151628',
|
||||
url: 'https://paradex.io/',
|
||||
keywords: ['relayer'],
|
||||
instant: {
|
||||
orderSource: '',
|
||||
},
|
||||
},
|
||||
veil: {
|
||||
label: 'Veil',
|
||||
@ -45,6 +45,9 @@ const PROJECTS: { [s: string]: ExploreEntry } = {
|
||||
theme_color: '#262626',
|
||||
url: 'https://radarrelay.com/',
|
||||
keywords: ['relayer'],
|
||||
instant: {
|
||||
orderSource: 'https://api.radarrelay.com/0x/v2/',
|
||||
},
|
||||
},
|
||||
emoon: {
|
||||
label: 'Emoon',
|
||||
@ -72,6 +75,19 @@ const PROJECTS: { [s: string]: ExploreEntry } = {
|
||||
},
|
||||
};
|
||||
|
||||
enum ExploreFilterType {
|
||||
All = 'ALL',
|
||||
Keyword = 'Keyword',
|
||||
}
|
||||
|
||||
interface ExploreFilterMetadata {
|
||||
label: string;
|
||||
filterType: ExploreFilterType;
|
||||
name: string;
|
||||
keyword?: string;
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
const FILTERS: ExploreFilterMetadata[] = [{
|
||||
label: 'All',
|
||||
name: 'all',
|
||||
@ -94,12 +110,18 @@ enum ExploreEntriesModifiers {
|
||||
}
|
||||
|
||||
enum ExploreEntriesOrdering {
|
||||
None = 'NONE',
|
||||
None = 'None',
|
||||
Latest = 'Latest',
|
||||
Popular = 'Popular',
|
||||
}
|
||||
|
||||
const ORDERINGS = [ExploreEntriesOrdering.None, ExploreEntriesOrdering.Latest, ExploreEntriesOrdering.Popular];
|
||||
|
||||
export class Explore extends React.Component<ExploreProps> {
|
||||
public state = {
|
||||
isEntriesLoading: false,
|
||||
isContactModalOpen: false,
|
||||
tiles: [] as ExploreGridListTile[],
|
||||
entries: [] as RicherExploreEntry[],
|
||||
entriesOrdering: ExploreEntriesOrdering.None,
|
||||
filters: FILTERS,
|
||||
@ -124,10 +146,10 @@ export class Explore extends React.Component<ExploreProps> {
|
||||
<ExploreHero onSearch={this._changeSearchResults} />
|
||||
<Section padding={'0 0 120px 0'} maxWidth={'1150px'}>
|
||||
<ExploreToolBar onFilterClick={this._setFilter} filters={this.state.filters} />
|
||||
<ExploreGrid entries={this.state.entries} />
|
||||
<ExploreGrid tiles={this._generateTilesFromState()} />
|
||||
</Section>
|
||||
<Banner
|
||||
heading="Have a 0x project?"
|
||||
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' }}
|
||||
@ -135,13 +157,12 @@ export class Explore extends React.Component<ExploreProps> {
|
||||
<ModalContact
|
||||
isOpen={this.state.isContactModalOpen}
|
||||
onDismiss={this._onDismissContactModal}
|
||||
modalContactType={ModalContactType.Credits}
|
||||
modalContactType={ModalContactType.Explore}
|
||||
/>
|
||||
</SiteWrap>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private readonly _onOpenContactModal = (): void => {
|
||||
this.setState({ isContactModalOpen: true });
|
||||
};
|
||||
@ -150,10 +171,30 @@ export class Explore extends React.Component<ExploreProps> {
|
||||
this.setState({ isContactModalOpen: false });
|
||||
};
|
||||
|
||||
private _launchInstantAsync = async (): Promise<void> => {
|
||||
|
||||
private _launchInstantAsync = (params: ExploreEntryInstantMetadata): void => {
|
||||
zeroExInstant.render(params, 'body');
|
||||
};
|
||||
|
||||
private _generateTilesFromState = (): ExploreGridListTile[] => {
|
||||
if (this.state.isEntriesLoading) {
|
||||
return [{
|
||||
name: 'loading',
|
||||
component: <ExploreGridDialogTile {...EXPLORE_STATE_DIALOGS.LOADING} />,
|
||||
visibility: ExploreGridListTileVisibility.Visible,
|
||||
width: ExploreGridListTileWidth.FullWidth,
|
||||
}];
|
||||
}
|
||||
if (_.isEmpty(this.state.tiles.filter(t => !!t.exploreEntry && t.visibility !== ExploreGridListTileVisibility.Hidden))) {
|
||||
return [{
|
||||
name: 'empty',
|
||||
component: <ExploreGridDialogTile {...EXPLORE_STATE_DIALOGS.EMPTY} />,
|
||||
visibility: ExploreGridListTileVisibility.Visible,
|
||||
width: ExploreGridListTileWidth.FullWidth,
|
||||
}];
|
||||
}
|
||||
return this.state.tiles;
|
||||
}
|
||||
|
||||
private _changeSearchResults = (query: string): void => {
|
||||
this.setState({ query: query.trim().toLowerCase() }, () => {
|
||||
this._setEntriesModifier(ExploreEntriesModifiers.Search);
|
||||
@ -187,30 +228,32 @@ export class Explore extends React.Component<ExploreProps> {
|
||||
};
|
||||
|
||||
private _setEntriesModifier = async (modifier: ExploreEntriesModifiers): Promise<void> => {
|
||||
let newEntries: RicherExploreEntry[];
|
||||
let newTiles: ExploreGridListTile[];
|
||||
if (modifier === ExploreEntriesModifiers.Filter || modifier === ExploreEntriesModifiers.Search) {
|
||||
const activeFilters = _.filter(this.state.filters, f => f.active);
|
||||
if (activeFilters.length === 1 && activeFilters[0].name === 'all') {
|
||||
newEntries = _.concat([], this.state.entries).map(e => {
|
||||
const newEntry = _.assign({}, e);
|
||||
newEntry.visibility = ExploreEntryVisibility.Visible;
|
||||
if (modifier === ExploreEntriesModifiers.Search && newEntry.visibility === ExploreEntryVisibility.Visible) {
|
||||
newEntry.visibility = (_.includes(newEntry.label.toLowerCase(), this.state.query) && ExploreEntryVisibility.Visible) || ExploreEntryVisibility.Hidden;
|
||||
newTiles = _.concat([], this.state.tiles).map(t => {
|
||||
const newTile = _.assign({}, t);
|
||||
newTile.visibility = ExploreGridListTileVisibility.Visible;
|
||||
if (modifier === ExploreEntriesModifiers.Search && !!newTile.exploreEntry) {
|
||||
newTile.visibility = (_.includes(newTile.exploreEntry.label.toLowerCase(), this.state.query) && ExploreGridListTileVisibility.Visible) || ExploreGridListTileVisibility.Hidden;
|
||||
}
|
||||
return newEntry;
|
||||
return newTile;
|
||||
});
|
||||
} else {
|
||||
newEntries = _.concat([], this.state.entries).map(e => {
|
||||
const newEntry = _.assign({}, e);
|
||||
newEntry.visibility = _.intersectionWith(activeFilters, newEntry.keywords, (f, k) => k === f.name).length !== 0 ? ExploreEntryVisibility.Visible : ExploreEntryVisibility.Hidden;
|
||||
if (modifier === ExploreEntriesModifiers.Search && newEntry.visibility === ExploreEntryVisibility.Visible) {
|
||||
newEntry.visibility = (_.includes(newEntry.label.toLowerCase(), this.state.query) && ExploreEntryVisibility.Visible) || ExploreEntryVisibility.Hidden;
|
||||
newTiles = _.concat([], this.state.tiles).map(t => {
|
||||
const newTile = _.assign({}, t);
|
||||
if (!!newTile.exploreEntry) {
|
||||
newTile.visibility = _.intersectionWith(activeFilters, newTile.exploreEntry.keywords, (f, k) => k === f.name).length !== 0 ? ExploreGridListTileVisibility.Visible : ExploreGridListTileVisibility.Hidden;
|
||||
if (modifier === ExploreEntriesModifiers.Search && newTile.visibility === ExploreGridListTileVisibility.Visible) {
|
||||
newTile.visibility = (_.includes(newTile.exploreEntry.label.toLowerCase(), this.state.query) && ExploreGridListTileVisibility.Visible) || ExploreGridListTileVisibility.Hidden;
|
||||
}
|
||||
}
|
||||
return newEntry;
|
||||
return newTile;
|
||||
});
|
||||
}
|
||||
}
|
||||
this.setState({ entries: newEntries});
|
||||
this.setState({ tiles: newTiles });
|
||||
};
|
||||
|
||||
// For future versions, ordering can be determined by async processes
|
||||
@ -222,9 +265,21 @@ export class Explore extends React.Component<ExploreProps> {
|
||||
|
||||
// For future versions, the load entries function can be async
|
||||
private _loadEntriesAsync = async (): Promise<void> => {
|
||||
const rawEntries = _.values(PROJECTS).map(e => _.assign(e, { visibility: ExploreEntryVisibility.Visible})) as RicherExploreEntry[];
|
||||
const entries = await this._setEntriesOrderingAsync(rawEntries);
|
||||
this.setState({ entries });
|
||||
this.setState({ isEntriesLoading: true });
|
||||
const rawEntries = _.values(PROJECTS);
|
||||
const tiles = (await this._setEntriesOrderingAsync(rawEntries)).map(e => {
|
||||
const richExploreEntry = _.assign({}, e) as RicherExploreEntry;
|
||||
if (!!richExploreEntry.instant) {
|
||||
richExploreEntry.onInstantClick = () => this._launchInstantAsync(richExploreEntry.instant);
|
||||
}
|
||||
return {
|
||||
name: e.label.toLowerCase(),
|
||||
exploreEntry: richExploreEntry,
|
||||
visibility: ExploreGridListTileVisibility.Visible,
|
||||
width: ExploreGridListTileWidth.OneThird,
|
||||
};
|
||||
});
|
||||
this.setState({ entries: rawEntries, tiles, isEntriesLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,6 +292,7 @@ const ExploreHeroContentWrapper = styled.div`
|
||||
interface ExploreHeroProps {
|
||||
onSearch(query: string): void;
|
||||
}
|
||||
|
||||
const ExploreHero = (props: ExploreHeroProps) => {
|
||||
const onSearchDebounce = _.debounce(props.onSearch, 300);
|
||||
const onChange = (e: any) => { onSearchDebounce(e.target.value); };
|
||||
@ -272,14 +328,6 @@ interface ExploreToolBarProps {
|
||||
onFilterClick(filterName: string, active: boolean): void;
|
||||
}
|
||||
|
||||
const SettingsIconWrapper = styled.div`
|
||||
padding-right: 0.4rem;
|
||||
display: inline;
|
||||
& > * {
|
||||
transform: translateY(2px);
|
||||
}
|
||||
`;
|
||||
|
||||
const ExploreToolBar = (props: ExploreToolBarProps) => {
|
||||
return <ExploreToolBarWrapper>
|
||||
<ExploreToolBarContentWrapper>
|
||||
@ -289,12 +337,7 @@ const ExploreToolBar = (props: ExploreToolBarProps) => {
|
||||
})}
|
||||
</ExploreToolBarContentWrapper>
|
||||
<ExploreToolBarContentWrapper>
|
||||
<ExploreTagButton disableHover={true}>
|
||||
<SettingsIconWrapper>
|
||||
<Icon color={colors.grey} name="settings" size={16} />
|
||||
</SettingsIconWrapper>
|
||||
Featured
|
||||
</ExploreTagButton>
|
||||
<ExploreSettingsDropdown orderings={ORDERINGS}/>
|
||||
</ExploreToolBarContentWrapper>
|
||||
</ExploreToolBarWrapper>;
|
||||
};
|
||||
|
@ -0,0 +1,152 @@
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Icon } from 'ts/components/icon';
|
||||
import { Heading, Paragraph } from 'ts/components/text';
|
||||
import { Switch } from 'ts/components/ui/switch';
|
||||
import { Button as ExploreTagButton } from 'ts/pages/explore/explore_tag_button';
|
||||
import { colors } from 'ts/style/colors';
|
||||
|
||||
const ExploreSettingsDropdownButton = ({}) => {
|
||||
return <ExploreTagButton disableHover={true}>
|
||||
<SettingsIconWrapper>
|
||||
<Icon color={colors.grey} name="settings" size={16} />
|
||||
</SettingsIconWrapper>
|
||||
Settings
|
||||
</ExploreTagButton>;
|
||||
};
|
||||
|
||||
const SettingsIconWrapper = styled.div`
|
||||
padding-right: 0.4rem;
|
||||
display: inline;
|
||||
& > * {
|
||||
transform: translateY(2px);
|
||||
}
|
||||
`;
|
||||
|
||||
const SettingsWrapper = styled.div`
|
||||
position: relative;
|
||||
|
||||
@media (min-width: 800px) {
|
||||
&:hover > div {
|
||||
display: block;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
transition: opacity 0.35s, transform 0.35s, visibility 0s;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface DropdownWrapInterface {
|
||||
width?: number;
|
||||
}
|
||||
|
||||
const DropdownWrap = styled.div<DropdownWrapInterface>`
|
||||
width: ${props => props.width || 280}px;
|
||||
margin-top: 16px;
|
||||
padding: 16px 24px;
|
||||
border: 1px solid transparent;
|
||||
border-color: ${props => props.theme.dropdownBorderColor};
|
||||
background-color: ${props => props.theme.dropdownBg};
|
||||
color: ${props => props.theme.dropdownColor};
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0%;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transform: translate3d(0, -10px, 0);
|
||||
transition: opacity 0.35s, transform 0.35s, visibility 0s 0.35s;
|
||||
z-index: 20;
|
||||
|
||||
&:after,
|
||||
&:before {
|
||||
bottom: 100%;
|
||||
left: 90%;
|
||||
border: solid transparent;
|
||||
content: ' ';
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
&:after {
|
||||
border-color: rgba(255, 255, 255, 0);
|
||||
border-bottom-color: ${props => props.theme.dropdownBg};
|
||||
border-width: 10px;
|
||||
margin-left: -10px;
|
||||
}
|
||||
&:before {
|
||||
border-color: rgba(255, 0, 0, 0);
|
||||
border-bottom-color: ${props => props.theme.dropdownBorderColor};
|
||||
border-width: 11px;
|
||||
margin-left: -11px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export interface ExploreSettingsDropdownProps {
|
||||
orderings: string[];
|
||||
}
|
||||
|
||||
export class ExploreSettingsDropdown extends React.Component<ExploreSettingsDropdownProps> {
|
||||
constructor(props: ExploreSettingsDropdownProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
return <SettingsWrapper>
|
||||
<ExploreSettingsDropdownButton/>
|
||||
<DropdownWrap>
|
||||
<DropdownContent orderings={this.props.orderings}/>
|
||||
</DropdownWrap>
|
||||
</SettingsWrapper>;
|
||||
}
|
||||
}
|
||||
|
||||
const DropdownContentWrapper = styled.div`
|
||||
`;
|
||||
|
||||
const OrderingWrapper = styled.div`
|
||||
padding-top: 20px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: ${props => props.theme.dropdownColor};
|
||||
opacity: 0.15;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
interface DropdownContentProps {
|
||||
orderings: string[];
|
||||
}
|
||||
|
||||
const DropdownContent = (props: DropdownContentProps) => {
|
||||
return <DropdownContentWrapper>
|
||||
<div>
|
||||
<Switch label="Editorial"/>
|
||||
<Heading asElement="h4" size={14} color="inherit" marginBottom="0" isMuted={0.35}>
|
||||
Editorial content reflects the views of the 0x core team.
|
||||
</Heading>
|
||||
</div>
|
||||
<OrderingWrapper>
|
||||
<Heading asElement="h4" size={14} color="inherit" marginBottom="16px" isMuted={0.35}>
|
||||
Ordering
|
||||
</Heading>
|
||||
{props.orderings.map(o => {
|
||||
return <Paragraph marginBottom="12px" key={o}>{o}</Paragraph>;
|
||||
})}
|
||||
</OrderingWrapper>
|
||||
</DropdownContentWrapper>;
|
||||
};
|
@ -3,10 +3,35 @@ import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { ExploreGridTile } from 'ts/pages/explore/explore_grid_tile';
|
||||
import { ExploreEntryVisibility, RicherExploreEntry} from 'ts/types';
|
||||
import { RicherExploreEntry} from 'ts/types';
|
||||
|
||||
export interface ExptoreGridProps {
|
||||
entries: RicherExploreEntry[];
|
||||
tiles: ExploreGridListTile[];
|
||||
}
|
||||
|
||||
export enum ExploreGridListTileVisibility {
|
||||
Hidden = 'HIDDEN',
|
||||
Visible = 'VISIBLE',
|
||||
}
|
||||
|
||||
export enum ExploreGridListTileWidth {
|
||||
OneThird = 2,
|
||||
FullWidth = 6,
|
||||
Half = 3,
|
||||
TwoThirds = 4,
|
||||
}
|
||||
|
||||
export interface ExploreGridListTile {
|
||||
name: string;
|
||||
visibility: ExploreGridListTileVisibility;
|
||||
width?: ExploreGridListTileWidth;
|
||||
exploreEntry?: RicherExploreEntry;
|
||||
component?: React.ReactNode;
|
||||
}
|
||||
|
||||
interface RicherExploreGridListTile extends ExploreGridListTile {
|
||||
gridStart: number;
|
||||
gridEnd: number;
|
||||
}
|
||||
|
||||
export class ExploreGrid extends React.Component<ExptoreGridProps> {
|
||||
@ -17,21 +42,58 @@ export class ExploreGrid extends React.Component<ExptoreGridProps> {
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
<ExploreGridList>
|
||||
{this.props.entries.filter(e => e.visibility !== ExploreEntryVisibility.Hidden).map(e => {
|
||||
return <ExploreGridTile {...e} key={e.label} />;
|
||||
})}
|
||||
{this._prepareTiles().map(t => {
|
||||
if (!!t.exploreEntry) {
|
||||
return <ExploreGridTileWrapper key={t.name} gridStart={t.gridStart} gridEnd={t.gridEnd}>
|
||||
<ExploreGridTile {...t.exploreEntry} />
|
||||
</ExploreGridTileWrapper>;
|
||||
} else {
|
||||
return <ExploreGridTileWrapper key={t.name} gridStart={t.gridStart} gridEnd={t.gridEnd}>
|
||||
{!!t.component && t.component}
|
||||
</ExploreGridTileWrapper>;
|
||||
}
|
||||
})}
|
||||
</ExploreGridList>
|
||||
);
|
||||
}
|
||||
|
||||
private _prepareTiles = (): RicherExploreGridListTile[] => {
|
||||
const visibleTiles = this.props.tiles.filter(t => t.visibility !== ExploreGridListTileVisibility.Hidden);
|
||||
return this._generateGridValues(visibleTiles);
|
||||
}
|
||||
|
||||
private _generateGridValues = (tiles: ExploreGridListTile[]): RicherExploreGridListTile[] => {
|
||||
let gridEndCounter = 1;
|
||||
const richerTiles = tiles.map(t => {
|
||||
if (gridEndCounter + t.width > (ExploreGridListTileWidth.FullWidth + 1)) {
|
||||
gridEndCounter = 1;
|
||||
}
|
||||
const gridStart = gridEndCounter;
|
||||
const gridEnd = gridEndCounter + t.width;
|
||||
gridEndCounter = gridEnd;
|
||||
const newTile = _.assign({ gridStart, gridEnd }, t);
|
||||
return newTile as RicherExploreGridListTile;
|
||||
});
|
||||
return richerTiles;
|
||||
}
|
||||
}
|
||||
|
||||
interface ExploreGridListProps {
|
||||
|
||||
}
|
||||
|
||||
interface ExploreGridTileWrapperProps {
|
||||
gridStart: number;
|
||||
gridEnd: number;
|
||||
}
|
||||
|
||||
const ExploreGridTileWrapper = styled.div<ExploreGridTileWrapperProps>`
|
||||
grid-column-start: ${props => props.gridStart};
|
||||
grid-column-end: ${props => props.gridEnd};
|
||||
`;
|
||||
|
||||
const ExploreGridList = styled.div<ExploreGridListProps>`
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(${ExploreGridListTileWidth.FullWidth}, 1fr);
|
||||
grid-column-gap: 1.5rem;
|
||||
grid-row-gap: 1.5rem;
|
||||
@media (max-width: 56rem) {
|
||||
|
@ -0,0 +1,46 @@
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Button } from 'ts/components/button';
|
||||
import { Heading, Paragraph } from 'ts/components/text';
|
||||
import { Image } from 'ts/components/ui/image';
|
||||
import { ExploreGridTileWrapper } from 'ts/pages/explore/explore_grid_tile';
|
||||
|
||||
export interface ExploreGridDialogTileProps {
|
||||
dialogImageUrl?: string;
|
||||
title?: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const EXPLORE_STATE_DIALOGS: { [s: string]: ExploreGridDialogTileProps } = {
|
||||
ERROR: {
|
||||
title: 'Something went wrong.',
|
||||
description: 'Try refreshing the page after a few moments',
|
||||
},
|
||||
LOADING: {
|
||||
description: 'Loading...',
|
||||
},
|
||||
EMPTY: {
|
||||
title: 'No projects found.',
|
||||
description: 'Try deselecting a few tags or changing your search.',
|
||||
},
|
||||
};
|
||||
|
||||
export const ExploreGridDialogTile = (props: ExploreGridDialogTileProps) => {
|
||||
return <ExploreGridDialogTileWrapper>
|
||||
{!!props.dialogImageUrl && <Image
|
||||
src={props.dialogImageUrl}
|
||||
height={'90px'}
|
||||
/>}
|
||||
{!!props.title && <Heading marginBottom={'0.5rem'} size={'small'}>{props.title}</Heading>}
|
||||
<Paragraph marginBottom={'0.5rem'}>{props.description}</Paragraph>
|
||||
</ExploreGridDialogTileWrapper>;
|
||||
};
|
||||
|
||||
const ExploreGridDialogTileWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
margin: 120px 0;
|
||||
`;
|
@ -47,15 +47,15 @@ const ExploreGridTileLink = styled.a`
|
||||
display: block;
|
||||
`;
|
||||
|
||||
const ExploreGridTileWrapper = styled.div`
|
||||
export const ExploreGridTileWrapper = styled.div`
|
||||
display: block;
|
||||
position: relative;
|
||||
background-color: white;
|
||||
box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.1);
|
||||
transition: box-shadow 200ms ease-in-out;
|
||||
&:hover {
|
||||
box-shadow: 0px 8px 24px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
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);
|
||||
// }
|
||||
`;
|
||||
|
||||
const ExploreGridButtonWrapper = styled.div`
|
||||
|
@ -256,30 +256,10 @@ export interface ExploreEntry {
|
||||
instant?: ExploreEntryInstantMetadata;
|
||||
}
|
||||
|
||||
export enum ExploreEntryVisibility {
|
||||
Hidden = 'HIDDEN',
|
||||
Featured = 'FEATURED', // Temporarily unused feature
|
||||
Visible = 'VISIBLE',
|
||||
}
|
||||
|
||||
export interface RicherExploreEntry extends ExploreEntry {
|
||||
visibility: ExploreEntryVisibility;
|
||||
onInstantClick?(): void;
|
||||
}
|
||||
|
||||
export enum ExploreFilterType {
|
||||
All = 'ALL',
|
||||
Keyword = 'Keyword',
|
||||
}
|
||||
|
||||
export interface ExploreFilterMetadata {
|
||||
label: string;
|
||||
filterType: ExploreFilterType;
|
||||
name: string;
|
||||
keyword?: string;
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
export interface FAQQuestion {
|
||||
prompt: string;
|
||||
answer: React.ReactNode;
|
||||
|
@ -18,6 +18,9 @@ const config = {
|
||||
chunkFilename: 'bundle-[name].js',
|
||||
publicPath: '/',
|
||||
},
|
||||
externals: {
|
||||
zeroExInstant: 'zeroExInstant'
|
||||
},
|
||||
devtool: 'source-map',
|
||||
resolve: {
|
||||
modules: [path.join(__dirname, '/ts'), 'node_modules'],
|
||||
|
Loading…
x
Reference in New Issue
Block a user