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_padding.css" />
|
||||||
<link rel="stylesheet" href="/css/basscss_responsive_margin.css" />
|
<link rel="stylesheet" href="/css/basscss_responsive_margin.css" />
|
||||||
<link rel="stylesheet" href="/css/basscss_responsive_type_scale.css" />
|
<link rel="stylesheet" href="/css/basscss_responsive_type_scale.css" />
|
||||||
|
<script src="https://instant.0x.org/instant.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body style="margin: 0px; min-width: 355px;">
|
<body style="margin: 0px; min-width: 355px;">
|
||||||
|
@ -18,6 +18,7 @@ export enum ModalContactType {
|
|||||||
General = 'GENERAL',
|
General = 'GENERAL',
|
||||||
MarketMaker = 'MARKET_MAKER',
|
MarketMaker = 'MARKET_MAKER',
|
||||||
Credits = 'CREDITS',
|
Credits = 'CREDITS',
|
||||||
|
Explore = 'EXPLORE',
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ServiceOptionMetadata {
|
interface ServiceOptionMetadata {
|
||||||
@ -142,6 +143,8 @@ export class ModalContact extends React.Component<Props> {
|
|||||||
return this._renderMarketMakerFormContent(errors);
|
return this._renderMarketMakerFormContent(errors);
|
||||||
case ModalContactType.Credits:
|
case ModalContactType.Credits:
|
||||||
return this._renderCreditsFormContent(errors);
|
return this._renderCreditsFormContent(errors);
|
||||||
|
case ModalContactType.Explore:
|
||||||
|
return this._renderExploreFormContent(errors);
|
||||||
case ModalContactType.General:
|
case ModalContactType.General:
|
||||||
default:
|
default:
|
||||||
return this._renderGeneralFormContent(errors);
|
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 {
|
private _renderCreditsFormContent(errors: ErrorProps): React.ReactNode {
|
||||||
return (
|
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-responsive';
|
||||||
declare module 'react-scrollable-anchor';
|
declare module 'react-scrollable-anchor';
|
||||||
declare module 'react-headroom';
|
declare module 'react-headroom';
|
||||||
|
declare module 'zeroExInstant';
|
||||||
|
|
||||||
declare module '*.json' {
|
declare module '*.json' {
|
||||||
const json: any;
|
const json: any;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import * as zeroExInstant from 'zeroExInstant';
|
||||||
|
|
||||||
import { Banner } from 'ts/components/banner';
|
import { Banner } from 'ts/components/banner';
|
||||||
import { DocumentTitle } from 'ts/components/document_title';
|
import { DocumentTitle } from 'ts/components/document_title';
|
||||||
@ -10,11 +11,13 @@ import { Section } from 'ts/components/newLayout';
|
|||||||
import { SiteWrap } from 'ts/components/siteWrap';
|
import { SiteWrap } from 'ts/components/siteWrap';
|
||||||
import { Heading } from 'ts/components/text';
|
import { Heading } from 'ts/components/text';
|
||||||
import { Input as SearchInput } from 'ts/components/ui/search_textfield';
|
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 { Button as ExploreTagButton } from 'ts/pages/explore/explore_tag_button';
|
||||||
import { colors } from 'ts/style/colors';
|
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 { documentConstants } from 'ts/utils/document_meta_constants';
|
||||||
|
import { ExploreSettingsDropdown } from 'ts/pages/explore/explore_dropdown';
|
||||||
|
|
||||||
export interface ExploreProps {}
|
export interface ExploreProps {}
|
||||||
|
|
||||||
@ -26,9 +29,6 @@ const PROJECTS: { [s: string]: ExploreEntry } = {
|
|||||||
theme_color: '#151628',
|
theme_color: '#151628',
|
||||||
url: 'https://paradex.io/',
|
url: 'https://paradex.io/',
|
||||||
keywords: ['relayer'],
|
keywords: ['relayer'],
|
||||||
instant: {
|
|
||||||
orderSource: '',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
veil: {
|
veil: {
|
||||||
label: 'Veil',
|
label: 'Veil',
|
||||||
@ -45,6 +45,9 @@ const PROJECTS: { [s: string]: ExploreEntry } = {
|
|||||||
theme_color: '#262626',
|
theme_color: '#262626',
|
||||||
url: 'https://radarrelay.com/',
|
url: 'https://radarrelay.com/',
|
||||||
keywords: ['relayer'],
|
keywords: ['relayer'],
|
||||||
|
instant: {
|
||||||
|
orderSource: 'https://api.radarrelay.com/0x/v2/',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
emoon: {
|
emoon: {
|
||||||
label: '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[] = [{
|
const FILTERS: ExploreFilterMetadata[] = [{
|
||||||
label: 'All',
|
label: 'All',
|
||||||
name: 'all',
|
name: 'all',
|
||||||
@ -94,12 +110,18 @@ enum ExploreEntriesModifiers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum ExploreEntriesOrdering {
|
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> {
|
export class Explore extends React.Component<ExploreProps> {
|
||||||
public state = {
|
public state = {
|
||||||
|
isEntriesLoading: false,
|
||||||
isContactModalOpen: false,
|
isContactModalOpen: false,
|
||||||
|
tiles: [] as ExploreGridListTile[],
|
||||||
entries: [] as RicherExploreEntry[],
|
entries: [] as RicherExploreEntry[],
|
||||||
entriesOrdering: ExploreEntriesOrdering.None,
|
entriesOrdering: ExploreEntriesOrdering.None,
|
||||||
filters: FILTERS,
|
filters: FILTERS,
|
||||||
@ -124,10 +146,10 @@ export class Explore extends React.Component<ExploreProps> {
|
|||||||
<ExploreHero onSearch={this._changeSearchResults} />
|
<ExploreHero onSearch={this._changeSearchResults} />
|
||||||
<Section padding={'0 0 120px 0'} maxWidth={'1150px'}>
|
<Section padding={'0 0 120px 0'} maxWidth={'1150px'}>
|
||||||
<ExploreToolBar onFilterClick={this._setFilter} filters={this.state.filters} />
|
<ExploreToolBar onFilterClick={this._setFilter} filters={this.state.filters} />
|
||||||
<ExploreGrid entries={this.state.entries} />
|
<ExploreGrid tiles={this._generateTilesFromState()} />
|
||||||
</Section>
|
</Section>
|
||||||
<Banner
|
<Banner
|
||||||
heading="Have a 0x project?"
|
heading="Working on a 0x project?"
|
||||||
subline="Lorem Ipsum something then that and say something more."
|
subline="Lorem Ipsum something then that and say something more."
|
||||||
mainCta={{ text: 'Apply Now', onClick: this._onOpenContactModal }}
|
mainCta={{ text: 'Apply Now', onClick: this._onOpenContactModal }}
|
||||||
secondaryCta={{ text: 'Join Discord', href: 'https://discordapp.com/invite/d3FTX3M' }}
|
secondaryCta={{ text: 'Join Discord', href: 'https://discordapp.com/invite/d3FTX3M' }}
|
||||||
@ -135,13 +157,12 @@ export class Explore extends React.Component<ExploreProps> {
|
|||||||
<ModalContact
|
<ModalContact
|
||||||
isOpen={this.state.isContactModalOpen}
|
isOpen={this.state.isContactModalOpen}
|
||||||
onDismiss={this._onDismissContactModal}
|
onDismiss={this._onDismissContactModal}
|
||||||
modalContactType={ModalContactType.Credits}
|
modalContactType={ModalContactType.Explore}
|
||||||
/>
|
/>
|
||||||
</SiteWrap>
|
</SiteWrap>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private readonly _onOpenContactModal = (): void => {
|
private readonly _onOpenContactModal = (): void => {
|
||||||
this.setState({ isContactModalOpen: true });
|
this.setState({ isContactModalOpen: true });
|
||||||
};
|
};
|
||||||
@ -150,10 +171,30 @@ export class Explore extends React.Component<ExploreProps> {
|
|||||||
this.setState({ isContactModalOpen: false });
|
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 => {
|
private _changeSearchResults = (query: string): void => {
|
||||||
this.setState({ query: query.trim().toLowerCase() }, () => {
|
this.setState({ query: query.trim().toLowerCase() }, () => {
|
||||||
this._setEntriesModifier(ExploreEntriesModifiers.Search);
|
this._setEntriesModifier(ExploreEntriesModifiers.Search);
|
||||||
@ -187,30 +228,32 @@ export class Explore extends React.Component<ExploreProps> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private _setEntriesModifier = async (modifier: ExploreEntriesModifiers): Promise<void> => {
|
private _setEntriesModifier = async (modifier: ExploreEntriesModifiers): Promise<void> => {
|
||||||
let newEntries: RicherExploreEntry[];
|
let newTiles: ExploreGridListTile[];
|
||||||
if (modifier === ExploreEntriesModifiers.Filter || modifier === ExploreEntriesModifiers.Search) {
|
if (modifier === ExploreEntriesModifiers.Filter || modifier === ExploreEntriesModifiers.Search) {
|
||||||
const activeFilters = _.filter(this.state.filters, f => f.active);
|
const activeFilters = _.filter(this.state.filters, f => f.active);
|
||||||
if (activeFilters.length === 1 && activeFilters[0].name === 'all') {
|
if (activeFilters.length === 1 && activeFilters[0].name === 'all') {
|
||||||
newEntries = _.concat([], this.state.entries).map(e => {
|
newTiles = _.concat([], this.state.tiles).map(t => {
|
||||||
const newEntry = _.assign({}, e);
|
const newTile = _.assign({}, t);
|
||||||
newEntry.visibility = ExploreEntryVisibility.Visible;
|
newTile.visibility = ExploreGridListTileVisibility.Visible;
|
||||||
if (modifier === ExploreEntriesModifiers.Search && newEntry.visibility === ExploreEntryVisibility.Visible) {
|
if (modifier === ExploreEntriesModifiers.Search && !!newTile.exploreEntry) {
|
||||||
newEntry.visibility = (_.includes(newEntry.label.toLowerCase(), this.state.query) && ExploreEntryVisibility.Visible) || ExploreEntryVisibility.Hidden;
|
newTile.visibility = (_.includes(newTile.exploreEntry.label.toLowerCase(), this.state.query) && ExploreGridListTileVisibility.Visible) || ExploreGridListTileVisibility.Hidden;
|
||||||
}
|
}
|
||||||
return newEntry;
|
return newTile;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
newEntries = _.concat([], this.state.entries).map(e => {
|
newTiles = _.concat([], this.state.tiles).map(t => {
|
||||||
const newEntry = _.assign({}, e);
|
const newTile = _.assign({}, t);
|
||||||
newEntry.visibility = _.intersectionWith(activeFilters, newEntry.keywords, (f, k) => k === f.name).length !== 0 ? ExploreEntryVisibility.Visible : ExploreEntryVisibility.Hidden;
|
if (!!newTile.exploreEntry) {
|
||||||
if (modifier === ExploreEntriesModifiers.Search && newEntry.visibility === ExploreEntryVisibility.Visible) {
|
newTile.visibility = _.intersectionWith(activeFilters, newTile.exploreEntry.keywords, (f, k) => k === f.name).length !== 0 ? ExploreGridListTileVisibility.Visible : ExploreGridListTileVisibility.Hidden;
|
||||||
newEntry.visibility = (_.includes(newEntry.label.toLowerCase(), this.state.query) && ExploreEntryVisibility.Visible) || ExploreEntryVisibility.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
|
// 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
|
// For future versions, the load entries function can be async
|
||||||
private _loadEntriesAsync = async (): Promise<void> => {
|
private _loadEntriesAsync = async (): Promise<void> => {
|
||||||
const rawEntries = _.values(PROJECTS).map(e => _.assign(e, { visibility: ExploreEntryVisibility.Visible})) as RicherExploreEntry[];
|
this.setState({ isEntriesLoading: true });
|
||||||
const entries = await this._setEntriesOrderingAsync(rawEntries);
|
const rawEntries = _.values(PROJECTS);
|
||||||
this.setState({ entries });
|
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 {
|
interface ExploreHeroProps {
|
||||||
onSearch(query: string): void;
|
onSearch(query: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExploreHero = (props: ExploreHeroProps) => {
|
const ExploreHero = (props: ExploreHeroProps) => {
|
||||||
const onSearchDebounce = _.debounce(props.onSearch, 300);
|
const onSearchDebounce = _.debounce(props.onSearch, 300);
|
||||||
const onChange = (e: any) => { onSearchDebounce(e.target.value); };
|
const onChange = (e: any) => { onSearchDebounce(e.target.value); };
|
||||||
@ -272,14 +328,6 @@ interface ExploreToolBarProps {
|
|||||||
onFilterClick(filterName: string, active: boolean): void;
|
onFilterClick(filterName: string, active: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SettingsIconWrapper = styled.div`
|
|
||||||
padding-right: 0.4rem;
|
|
||||||
display: inline;
|
|
||||||
& > * {
|
|
||||||
transform: translateY(2px);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ExploreToolBar = (props: ExploreToolBarProps) => {
|
const ExploreToolBar = (props: ExploreToolBarProps) => {
|
||||||
return <ExploreToolBarWrapper>
|
return <ExploreToolBarWrapper>
|
||||||
<ExploreToolBarContentWrapper>
|
<ExploreToolBarContentWrapper>
|
||||||
@ -289,12 +337,7 @@ const ExploreToolBar = (props: ExploreToolBarProps) => {
|
|||||||
})}
|
})}
|
||||||
</ExploreToolBarContentWrapper>
|
</ExploreToolBarContentWrapper>
|
||||||
<ExploreToolBarContentWrapper>
|
<ExploreToolBarContentWrapper>
|
||||||
<ExploreTagButton disableHover={true}>
|
<ExploreSettingsDropdown orderings={ORDERINGS}/>
|
||||||
<SettingsIconWrapper>
|
|
||||||
<Icon color={colors.grey} name="settings" size={16} />
|
|
||||||
</SettingsIconWrapper>
|
|
||||||
Featured
|
|
||||||
</ExploreTagButton>
|
|
||||||
</ExploreToolBarContentWrapper>
|
</ExploreToolBarContentWrapper>
|
||||||
</ExploreToolBarWrapper>;
|
</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 styled from 'styled-components';
|
||||||
|
|
||||||
import { ExploreGridTile } from 'ts/pages/explore/explore_grid_tile';
|
import { ExploreGridTile } from 'ts/pages/explore/explore_grid_tile';
|
||||||
import { ExploreEntryVisibility, RicherExploreEntry} from 'ts/types';
|
import { RicherExploreEntry} from 'ts/types';
|
||||||
|
|
||||||
export interface ExptoreGridProps {
|
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> {
|
export class ExploreGrid extends React.Component<ExptoreGridProps> {
|
||||||
@ -17,21 +42,58 @@ export class ExploreGrid extends React.Component<ExptoreGridProps> {
|
|||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<ExploreGridList>
|
<ExploreGridList>
|
||||||
{this.props.entries.filter(e => e.visibility !== ExploreEntryVisibility.Hidden).map(e => {
|
{this._prepareTiles().map(t => {
|
||||||
return <ExploreGridTile {...e} key={e.label} />;
|
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>
|
</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 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>`
|
const ExploreGridList = styled.div<ExploreGridListProps>`
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(${ExploreGridListTileWidth.FullWidth}, 1fr);
|
||||||
grid-column-gap: 1.5rem;
|
grid-column-gap: 1.5rem;
|
||||||
grid-row-gap: 1.5rem;
|
grid-row-gap: 1.5rem;
|
||||||
@media (max-width: 56rem) {
|
@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;
|
display: block;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ExploreGridTileWrapper = styled.div`
|
export const ExploreGridTileWrapper = styled.div`
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.1);
|
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||||
transition: box-shadow 200ms ease-in-out;
|
// transition: box-shadow 200ms ease-in-out;
|
||||||
&:hover {
|
// &:hover {
|
||||||
box-shadow: 0px 8px 24px rgba(0, 0, 0, 0.1);
|
// box-shadow: 0px 8px 24px rgba(0, 0, 0, 0.1);
|
||||||
}
|
// }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ExploreGridButtonWrapper = styled.div`
|
const ExploreGridButtonWrapper = styled.div`
|
||||||
|
@ -256,30 +256,10 @@ export interface ExploreEntry {
|
|||||||
instant?: ExploreEntryInstantMetadata;
|
instant?: ExploreEntryInstantMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ExploreEntryVisibility {
|
|
||||||
Hidden = 'HIDDEN',
|
|
||||||
Featured = 'FEATURED', // Temporarily unused feature
|
|
||||||
Visible = 'VISIBLE',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RicherExploreEntry extends ExploreEntry {
|
export interface RicherExploreEntry extends ExploreEntry {
|
||||||
visibility: ExploreEntryVisibility;
|
|
||||||
onInstantClick?(): void;
|
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 {
|
export interface FAQQuestion {
|
||||||
prompt: string;
|
prompt: string;
|
||||||
answer: React.ReactNode;
|
answer: React.ReactNode;
|
||||||
|
@ -18,6 +18,9 @@ const config = {
|
|||||||
chunkFilename: 'bundle-[name].js',
|
chunkFilename: 'bundle-[name].js',
|
||||||
publicPath: '/',
|
publicPath: '/',
|
||||||
},
|
},
|
||||||
|
externals: {
|
||||||
|
zeroExInstant: 'zeroExInstant'
|
||||||
|
},
|
||||||
devtool: 'source-map',
|
devtool: 'source-map',
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: [path.join(__dirname, '/ts'), 'node_modules'],
|
modules: [path.join(__dirname, '/ts'), 'node_modules'],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user