changed to popular

This commit is contained in:
David Sun 2019-03-06 10:19:51 -08:00
parent f72918362d
commit c642cd6fed
12 changed files with 480 additions and 76 deletions

Binary file not shown.

View File

@ -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;">

View File

@ -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 youre 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 (
<> <>

View 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>;
};

View File

@ -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;

View File

@ -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>;
}; };

View File

@ -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>;
};

View File

@ -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) {

View File

@ -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;
`;

View File

@ -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`

View File

@ -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;

View File

@ -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'],