Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reusable components #347

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion static-site/gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ module.exports = {
// "gatsby-plugin-catch-links" // See https://github.com/nextstrain/nextstrain.org/issues/34
],
developMiddleware: app => {
['/charon', '/dist', ...potentialAuspiceRoutes].forEach(path => {
['/charon', '/whoami', '/dist', ...potentialAuspiceRoutes].forEach(path => {
app.use(
path,
createProxyMiddleware({
Expand Down
61 changes: 61 additions & 0 deletions static-site/src/components/Blurb/Blurb.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import styled from "styled-components";
import React from "react";
import {PanelButtons} from "../panels/panelStyles";

const Blurb = ({title, buttons, children}) => (
<OuterContainer>
<InnerContainer>
{title && (<Title>{title}</Title>)}
{children}
{buttons && (
<div style={{paddingTop: "15px"}}>
<PanelButtons buttons={buttons}/>
</div>
)}
</InnerContainer>
</OuterContainer>
);

export default Blurb;

const OuterContainer = styled.div`
min-width: 100%;
display: flex;
flex-direction: row;
justify-content: space-around;
margin-bottom: 50px;
@media (max-width: ${(props) => props.theme.mobileThreshold}) {
margin-bottom: 30px;
}
border: 1px solid #c994c7;
`;

const InnerContainer = styled.div`
max-width: 80%;
margin: 0px auto;
text-align: center;
font-size: ${(props) => props.theme.niceFontSize};
font-weight: 300;
line-height: ${(props) => props.theme.niceLineHeight};
border: 1px solid #8856a7;
@media (max-width: ${(props) => props.theme.mobileThreshold}) {
max-width: 100%;
}
li {
text-align: left;
}
`;

const Title = styled.div`
text-align: center;
font-size: 38px;
line-height: 50px;
font-weight: 300;
font-family: ${(props) => props.theme.generalFont};
color: ${(props) => props.theme.darkGrey};
margin: 5px 0px;
@media (max-width: ${(props) => props.theme.mobileThreshold}) {
font-size: 32px;
line-height: 42px;
}
`;
101 changes: 101 additions & 0 deletions static-site/src/components/panels/card.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React from "react";
import PropTypes from "prop-types";
import styled, {css} from "styled-components";
import {theme} from "../../layouts/theme";

// eslint-disable-next-line react/prefer-stateless-function
class Card extends React.Component {
calcDim() {
/* more experimentation needed with different screen sizes here */
if (this.props.parentWidth > 500) {
return 200;
}
return 140; // minimum card size
}
render() {
if (!this.props.parentWidth) return null;
const color = theme.rainbow10[this.props.cardIdx%theme.rainbow10.length];
const dim = this.calcDim();
return (
<Container dim={dim}>
<a href={this.props.url}>
{this.props.img ?
<CardImg src={require(`../../../static/splash_images/${this.props.img}`)} as={this.props.title}/> : // eslint-disable-line
<CardSwatch color={color}/>
}
<CardTitle availWidth={dim}>{this.props.title}</CardTitle>
{this.props.private && <Padlock availWidth={dim}/>}
</a>
</Container>
);
}
}

Card.propTypes = {
title: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
img: PropTypes.string,
private: PropTypes.bool,
cardIdx: PropTypes.number.isRequired,
parentWidth: PropTypes.number.isRequired,
};

export default Card;

const Container = styled.div`
position: relative; // needed for children using absolute
overflow: hidden;
min-width: ${(props) => props.dim}px;
max-width: ${(props) => props.dim}px;
min-height: ${(props) => props.dim}px;
max-height: ${(props) => props.dim}px;
padding: 6px;
`;

const commonCardCss = css`
border-radius: 6px;
box-shadow: 3px 3px 3px 1px rgba(0, 0, 0, 0.15);
min-height: 100%;
max-height: 100%;
min-width: 100%;
max-width: 100%;
`;
const CardSwatch = styled.div`
${commonCardCss}
background-color: ${(props) => props.color};
`;
const CardImg = styled.img`
${commonCardCss}
`;

const overlayStyles = css`
position: absolute;
border-radius: ${(props) => props.availWidth<200 ? "3px" : "6px"};
padding: ${(props) => props.availWidth<200 ? "3px 5px" : "6px 12px"};
right: ${(props) => props.availWidth<200 ? "5px" : "12px"};
background: rgba(0, 0, 0, 0.7);
`;
const CardTitle = styled.div`
${overlayStyles}
top: ${(props) => props.availWidth<200 ? "20px" : "40px"};
max-width: ${(props) => props.availWidth - (props.availWidth<200 ? 20 : 40)}px;
font-family: ${(props) => props.theme.generalFont};
font-weight: ${(props) => props.availWidth<200 ? 300 : 500};
font-size: ${(props) => props.availWidth<200 ? '14px' : '24px'};
color: white;
`;
const PadlockContainer = styled.div`
${overlayStyles}
bottom: ${(props) => props.availWidth<200 ? "10px" : "30px"};
`;
const Padlock = ({availWidth}) => (
<PadlockContainer availWidth={availWidth}>
<svg stroke="white" fill="white" width="16" height="19">
<path
fillRule="evenodd"
d="M4 13H3v-1h1v1zm8-6v7c0 .55-.45 1-1 1H1c-.55 0-1-.45-1-1V7c0-.55.45-1 1-1h1V4c0-2.2 1.8-4 4-4s4 1.8 4 4v2h1c.55 0 1 .45 1 1zM3.8 6h4.41V4c0-1.22-.98-2.2-2.2-2.2-1.22 0-2.2.98-2.2 2.2v2H3.8zM11 7H2v7h9V7zM4 8H3v1h1V8zm0 2H3v1h1v-1z"
/>
</svg>
</PadlockContainer>
);

101 changes: 101 additions & 0 deletions static-site/src/components/panels/panelStyles.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/** Common styles imported by different panels */
import styled from "styled-components";
import React from "react";

export const PanelContainer = styled.div`
display: flex;
/* background-color: #80c7cd; */
border: 1px solid #80c7cd;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-around;
align-items: stretch; // each box in a row will be same height (cross-axis)
@media (max-width: ${(props) => props.theme.mobileThreshold}) {
flex-direction: column;
align-content: center;
}
`;


export const Panel = styled.div`
flex-grow: 1; // all same size
flex-shrink: 1;
min-width: ${(props) => props.fullwidth ? '100%' : '300px'};
max-width: ${(props) => props.fullwidth ? '100%' : '33%'}; // default for _huge_ screens is 3 columns
@media (max-width: ${(props) => props.theme.bigScreenThreshold}) {
max-width: ${(props) => props.fullwidth ? '100%' : '48%'}; // essentially two columns maximum. Not 50% to allow some margin.
}
@media (max-width: ${(props) => props.theme.mobileThreshold}) {
max-width: 100%; // for mobile take up all available width
}
min-height: 100%;
max-height: 100%;
border: 1px solid #b2efd8; // TODO - dev only
margin: 0px 0px 10px 0px;
`;


export const PanelTitle = styled.div`
text-align: center;
font-size: 2rem;
font-weight: 400;
font-family: ${(props) => props.theme.generalFont};
color: ${(props) => props.theme.darkGrey};
margin: 5px 0px;
`;

export const PanelSubtitle = styled.div`
text-align: center;
font-family: ${(props) => props.theme.generalFont};
font-size: ${(props) => props.theme.niceFontSize};
color: ${(props) => props.theme.medGrey};
margin: 5px 0px;
`;


export const PanelButtons = ({buttons}) => (
/**
* Render a row of buttons (often just one).
* Prop `buttons` is an array of objects, each with properties
* `to` (href), `label` (rendered text) `external` (optional boolean)
*/
<ButtonContainer>
{buttons.map(({to, label, external}) => (
<a href={to} target={external ? "_blank" : "_self"} rel="noopener noreferrer" key={label}>
<StyledButton>
{label}
</StyledButton>
</a>
))}
</ButtonContainer>
);

const ButtonContainer = styled.div`
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-evenly;
@media (max-width: ${(props) => props.theme.mobileThreshold}) {
flex-direction: column;
align-items: center;
}
`;

const StyledButton = styled.button`
border: 1px solid #CCC;
background-color: inherit;
border-radius: 3px;
cursor: pointer;
padding: 3px 10px 3px 10px; // between border & text
margin: 3px; // outside border
font-size: 1.8rem;
font-family: ${(props) => props.theme.generalFont};
color: ${(props) => props.theme.medGrey};
font-weight: 400;
text-transform: uppercase;
vertical-align: middle;
&:hover {
color: black;
border: 1px solid black;
}
`;
60 changes: 60 additions & 0 deletions static-site/src/components/panels/panelWithCards.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from "react";
import styled from "styled-components";
import { debounce } from 'lodash';
import Card from "./card";
import { Panel, PanelTitle, PanelSubtitle, PanelButtons } from "./panelStyles";


class PanelWithCards extends React.Component {
constructor() {
super();
this.state={cardsContainerWidth: 0};
this.updateDimensions = debounce(() => {
if (!this.cardsContainerRef) return;
this.setState({cardsContainerWidth: this.cardsContainerRef.getBoundingClientRect().width});
}, 100);
}
componentDidMount() {
this.updateDimensions();
window.addEventListener('resize', this.updateDimensions);
}
componentWillUnmount() {
window.removeEventListener('resize', this.updateDimensions);
}
render() {
return (
<Panel fullwidth={this.props.fullwidth}>
{this.props.title && (
<PanelTitle>{this.props.title}</PanelTitle>
)}
{this.props.subtitle && (
<PanelSubtitle>{this.props.subtitle}</PanelSubtitle>
)}
{this.props.cards && (
<CardsContainer innerRef={(input) => {this.cardsContainerRef = input;}}>
{/* we use `innerRef` to access the actual DOM node, as `ref` returns the styled component wrapper */}
{this.props.cards.map((props, cardIdx) =>
<Card {...props} cardIdx={cardIdx} parentWidth={this.state.cardsContainerWidth} key={props.title}/>
)}
</CardsContainer>
)}
{this.props.buttons && (<PanelButtons buttons={this.props.buttons}/>)}
</Panel>
);
}
}

export default PanelWithCards;


const CardsContainer = styled.div`
display: flex;
/* background-color: ; */
border: 1px solid #ffeda0;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-evenly;
align-items: stretch; // each box in a row will be same height (cross-axis)
margin: 20px 10px
`;

5 changes: 4 additions & 1 deletion static-site/src/layouts/theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,11 @@ export const theme = {
md: 64, // em
lg: 76 // em
}
}
},

// cross reference following with the thresholds we use in <GenericPage>
mobileThreshold: "775px", // todo - cross reference with auspice
bigScreenThreshold: '1500px' // todo - more testing needed

};

Expand Down
Loading