diff --git a/.storybook/config.js b/.storybook/config.js index 8f30b176a..b3ac690eb 100644 --- a/.storybook/config.js +++ b/.storybook/config.js @@ -9,6 +9,8 @@ import { BrandTheme } from "@hackoregon/component-library"; import { INITIAL_VIEWPORTS } from "@storybook/addon-viewport"; import themeCIVIC from "./themeCIVIC"; +const STORYBOOK_ENV = process.env.STORYBOOK_ENV || "default"; + addParameters({ options: { showPanel: true, @@ -23,7 +25,16 @@ addParameters({ }); function loadStories() { - require("../packages/component-library/stories"); + if (STORYBOOK_ENV === "new") { + const req = require.context( + "../packages", + true, + /^\.\/[^\/]+\/stories\/index.story.js$/ + ); + req.keys().forEach(filename => req(filename)); + } else { + require("../packages/component-library/stories"); + } } const withGlobal = cb => ( diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js index 68281c76f..9f979d505 100644 --- a/.storybook/webpack.config.js +++ b/.storybook/webpack.config.js @@ -27,7 +27,7 @@ module.exports = createConfig([ } ] }), - match(["*.js"], [storySourceLoader()]) + match(["*.story.js"], [storySourceLoader()]) ]); function storySourceLoader() { @@ -38,6 +38,7 @@ function storySourceLoader() { Object.assign( { test: /\.stories\.jsx?$/, + exclude: /\.node_modules\./, loaders: [require.resolve("@storybook/addon-storysource/loader")], enforce: "pre" }, diff --git a/lerna.json b/lerna.json index 40ac142fb..0ed92a29c 100644 --- a/lerna.json +++ b/lerna.json @@ -6,6 +6,9 @@ "command": { "publish": { "message": "(🧹release): publish %s" + }, + "version": { + "allowBranch": "master" } } } diff --git a/package.json b/package.json index a92b105d7..7a84460fa 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "publish-patch": "lerna publish bump patch --yes", "start": "node scripts/start", "storybook": "BABEL_ENV=esm start-storybook -p 6006", + "storybook-new": "BABEL_ENV=esm STORYBOOK_ENV=new start-storybook -p 6006", "test": "lerna exec yarn test --no-bail", "travis": "make travis", "watch": "node scripts/watch" @@ -78,7 +79,7 @@ "@storybook/addon-knobs": "^5.0.6", "@storybook/addon-links": "^5.0.6", "@storybook/addon-notes": "^5.0.6", - "@storybook/addon-storysource": "5.0.6", + "@storybook/addon-storysource": "^5.0.6", "@storybook/addon-viewport": "^5.0.11", "@storybook/react": "^5.0.6", "@storybook/storybook-deployer": "^2.3.0", @@ -147,4 +148,4 @@ "webpack-md5-hash": "^0.0.6", "webpack-node-externals": "^1.5.4" } -} \ No newline at end of file +} diff --git a/packages/2018/.toMove/src/components/CardDetail/CardDetailPage.js b/packages/2018/.toMove/src/components/CardDetail/CardDetailPage.js new file mode 100644 index 000000000..5feb97885 --- /dev/null +++ b/packages/2018/.toMove/src/components/CardDetail/CardDetailPage.js @@ -0,0 +1,46 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { Link } from "react-router"; +import { PageLayout } from "@hackoregon/ui-core"; +import { ExploreRelated } from ".."; + +const CardDetailPage = ({ params, CardRegistry }) => { + const card = CardRegistry.find(params.slug); + + if (card && card.component) { + const CardComponent = card.component; + return ( + + + + + ); + } + + return ( + +
+

Card not found

+

The card you are looking for doesn't exist.

+ Other cards +
+
+ ); +}; + +CardDetailPage.propTypes = { + params: PropTypes.shape({ slug: PropTypes.string.isRequired }), + CardRegistry: PropTypes.shape({ + entries: PropTypes.arrayOf( + PropTypes.shape({ + component: PropTypes.func.isRequired, + project: PropTypes.string.isRequired, + slug: PropTypes.string.isRequired + }) + ) + }).isRequired +}; + +CardDetailPage.displayName = "CardDetailPage"; + +export default CardDetailPage; diff --git a/packages/2018/.toMove/src/components/CardDetail/CardDetailPageEmbed.js b/packages/2018/.toMove/src/components/CardDetail/CardDetailPageEmbed.js new file mode 100644 index 000000000..d9681b747 --- /dev/null +++ b/packages/2018/.toMove/src/components/CardDetail/CardDetailPageEmbed.js @@ -0,0 +1,53 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { Link } from "react-router"; +import { get } from "lodash"; +import { + CivicCardLayoutClassic, + CivicCardLayoutPreview, + CivicCardLayoutVisualizationOnly, + CivicCardLayoutVisualizationOnlyNoLink, + CivicCardLayoutSideBySide +} from "@hackoregon/ui-cards"; + +const CardDetailPageEmbed = ({ params, CardRegistry }) => { + const card = CardRegistry.find(params.slug); + const layouts = { + visualization: CivicCardLayoutVisualizationOnly, + visualizationnolink: CivicCardLayoutVisualizationOnlyNoLink, + classic: CivicCardLayoutClassic, + preview: CivicCardLayoutPreview, + comparison: CivicCardLayoutSideBySide + }; + const Layout = get(layouts, params.layout) || layouts.classic; + + if (card && card.component) { + const CardComponent = card.component; + return ; + } + + return ( +
+

Card not found

+

The card you are looking for doesn't exist.

+ Find more on CIVIC +
+ ); +}; + +CardDetailPageEmbed.propTypes = { + params: PropTypes.shape({ slug: PropTypes.string.isRequired }), + CardRegistry: PropTypes.shape({ + entries: PropTypes.arrayOf( + PropTypes.shape({ + component: PropTypes.func.isRequired, + project: PropTypes.string.isRequired, + slug: PropTypes.string.isRequired + }) + ) + }).isRequired +}; + +CardDetailPageEmbed.displayName = "CardDetailPageEmbed"; + +export default CardDetailPageEmbed; diff --git a/packages/2018/.toMove/src/components/CardDetail/ExploreRelated.js b/packages/2018/.toMove/src/components/CardDetail/ExploreRelated.js new file mode 100644 index 000000000..f348d0fba --- /dev/null +++ b/packages/2018/.toMove/src/components/CardDetail/ExploreRelated.js @@ -0,0 +1,122 @@ +/** @jsx jsx */ +import { jsx, css } from "@emotion/core"; +import PropTypes from "prop-types"; +import shortid from "shortid"; +import { ThemeProvider } from "@material-ui/styles"; +import { MaterialTheme } from "@hackoregon/ui-themes"; +import { Placeholder } from "@hackoregon/ui-core"; +import { CivicCardLayoutPreviewTitleOnly } from "@hackoregon/ui-cards"; + +const sectionMarginSmall = css` + display: block; + margin: 12px auto; +`; + +const sectionMaxWidthSmall = css` + max-width: 900px; +`; + +const listContainerStyle = css` + width: 100%; + display: flex; + padding: 0; + justify-content: space-evenly; + flex-wrap: wrap; + box-sizing: border-box; +`; + +const listItemStyle = css` + width: 45%; + margin: 5px; + box-sizing: border-box; + list-style-type: none; +`; + +const matchRelatedCardsByTags = ( + slug, + baseTags, + entries, + numOfRelatedCards +) => { + const relatedCards = + baseTags && baseTags.length + ? entries + .map(entry => { + return { + numOfSimilarTags: entry.component.tags + ? entry.component.tags.filter(tag => { + return baseTags.includes(tag); + }).length + : 0, + ...entry + }; + }) + .filter(entry => { + return entry.slug !== slug; + }) + .sort((a, b) => { + return b.numOfSimilarTags - a.numOfSimilarTags; + }) + .slice(0, numOfRelatedCards) + : []; + + return relatedCards; +}; + +const ExploreRelated = ({ slug, CardRegistry, numOfRelatedCards = 4 }) => { + const card = CardRegistry.find(slug); + const relatedCards = matchRelatedCardsByTags( + slug, + card.component.tags, + CardRegistry.entries, + numOfRelatedCards + ); + + const relatedCardList = + relatedCards.length > 0 ? ( + relatedCards.map(relatedCard => ( +
  • + +
  • + )) + ) : ( + +

    + + See your data here! + +

    +
    + ); + + if (card && card.component) { + return ( + +
    +

    Explore related data

    +
      {relatedCardList}
    +
    +
    + ); + } + + return null; +}; + +ExploreRelated.propTypes = { + slug: PropTypes.string.isRequired, + CardRegistry: PropTypes.shape({ + entries: PropTypes.arrayOf( + PropTypes.shape({ + component: PropTypes.func.isRequired, + project: PropTypes.string.isRequired, + slug: PropTypes.string.isRequired + }) + ) + }).isRequired, + numOfRelatedCards: PropTypes.number +}; + +ExploreRelated.displayName = "ExploreRelated"; + +export default ExploreRelated; diff --git a/packages/2018/.toMove/src/components/CardList/CardList.js b/packages/2018/.toMove/src/components/CardList/CardList.js new file mode 100644 index 000000000..0fa160749 --- /dev/null +++ b/packages/2018/.toMove/src/components/CardList/CardList.js @@ -0,0 +1,308 @@ +/** @jsx jsx */ +import { css, jsx } from "@emotion/core"; +import { useState, Fragment } from "react"; +import PropTypes from "prop-types"; +import shortid from "shortid"; +import { Link } from "react-router"; + +import List from "@material-ui/core/List"; +import ListItem from "@material-ui/core/ListItem"; +import ListItemText from "@material-ui/core/ListItemText"; +import Collapse from "@material-ui/core/Collapse"; +import ExpandMore from "@material-ui/icons/ExpandMore"; +import ChevronRight from "@material-ui/icons/ChevronRight"; +import Drawer from "@material-ui/core/Drawer"; +import Hidden from "@material-ui/core/Hidden"; +import CssBaseline from "@material-ui/core/CssBaseline"; +import { makeStyles, useTheme } from "@material-ui/core/styles"; +import { ThemeProvider } from "@material-ui/styles"; +import { BrandColors, MaterialTheme } from "@hackoregon/ui-themes"; +import { Checkbox, Button } from "@hackoregon/ui-core"; +import { CivicCardLayoutPreview } from "@hackoregon/ui-cards"; + +import Header from "../Header/Header"; +import ProjectCard from "./ProjectCard"; +import cardListStyling from "./cardListStyling"; +import cardShouldShow from "./displayUtils"; + +const emptyState = css` + display: grid; + justify-content: center; + font-size: 1.2rem; + + > p { + > a { + color: ${BrandColors.action.hex}; + } + } +`; + +const useStyles = makeStyles(cardListStyling); + +const filterPadding = css` + padding-top: 1rem; + margin-bottom: 0; + padding-left: 1rem; + font-size: 1.5rem; +`; + +const headingPadding = css` + padding-left: 1rem; + padding-right: 1rem; +`; + +function deriveCategoryNamesFromTagsList(tagsList) { + return Object.keys(tagsList); +} + +function deriveInitialFilterStateFromCategories(filterCategories) { + const stateObject = {}; + filterCategories.forEach(category => { + stateObject[category] = []; + }); + return stateObject; +} + +function numberOfFiltersSelected(activeFilters, filterCategories) { + const flatListOfActiveFilters = filterCategories.reduce( + (accumulator, category) => [...accumulator, ...activeFilters[category]], + [] + ); + + return flatListOfActiveFilters.length; +} + +const CardList = ({ CardRegistry, tagsList, projects }) => { + // eslint-disable-next-line no-unused-vars + const { entries, tags } = CardRegistry; + + // eslint-disable-next-line no-console + console.log("Tag Count:", tags); + + const filterCategories = deriveCategoryNamesFromTagsList(tagsList); + + const classes = useStyles(); + const theme = useTheme(); + + // Nested List and Drawer open / close handlers + const [showAllStories, setShowAllStories] = useState(true); + const [mobileOpen, setMobileOpen] = useState(false); + const [openTopic, setOpenTopic] = useState(true); + const [openLocation, setOpenLocation] = useState(false); + const [openVisualization, setOpenVisualization] = useState(false); + + // holds state for checkboxes (nested object where specific tags are boolean) + const [activeFilters, setActiveFilters] = useState( + deriveInitialFilterStateFromCategories(filterCategories) + ); + + const categoryOpeners = { + topics: openTopic, + locations: openLocation, + visualizations: openVisualization + }; + + const categoryHandlers = { + topicsHandler() { + setOpenTopic(!openTopic); + }, + locationsHandler() { + setOpenLocation(!openLocation); + }, + visualizationsHandler() { + setOpenVisualization(!openVisualization); + } + }; + + const handleDrawerToggle = () => { + setMobileOpen(!mobileOpen); + }; + + const handleCheckboxChange = (category, id) => { + if (showAllStories) setShowAllStories(false); + if (activeFilters[category].includes(id)) { + setActiveFilters({ + ...activeFilters, + [category]: activeFilters[category].filter(tag => tag !== id) + }); + } else { + setActiveFilters({ + ...activeFilters, + [category]: [...activeFilters[category], id] + }); + } + }; + + const drawer = ( +
    +

    Filters

    + + {filterCategories.map(category => ( + + + c.toUpperCase())} + /> + {categoryOpeners[category] ? : } + + + + {tagsList[category].sort().map(id => ( + + handleCheckboxChange(category, id)} + /> + + ))} + + + + ))} + +
    + ); + + const filteredEntries = entries.filter(entry => + cardShouldShow( + entry.component.tags, + filterCategories, + activeFilters, + showAllStories, + numberOfFiltersSelected(activeFilters, filterCategories) + ) + ); + + return ( + +
    + +
    + + +
    +
    + +
    + {numberOfFiltersSelected(activeFilters, filterCategories) === 0 && ( +
    +

    + Featured Projects: Hack Oregon Demo Day +

    +
      + {projects.map(entry => ( +
    • + +
    • + ))} +
    +
    + )} + {filteredEntries.length > 0 ? ( +
    +

    + Cards: +

    +
      + {filteredEntries.map(entry => ( +
    • + { + + } +
    • + ))} +
    +
    + ) : ( +
    +

    + {`We haven't yet made any cards matching your selection.`} +
    + + Join us + {" "} + to make it happen! +

    +
    + )} +
    +
    +
    + + ); +}; + +CardList.displayName = "CardList"; +CardList.propTypes = { + CardRegistry: PropTypes.shape({}), + tagsList: PropTypes.shape({ + topics: PropTypes.arrayOf(PropTypes.string), + locations: PropTypes.arrayOf(PropTypes.string), + visualizations: PropTypes.arrayOf(PropTypes.string) + }), + projects: PropTypes.arrayOf( + PropTypes.shape({ + title: PropTypes.title, + description: PropTypes.description, + link: PropTypes.link + }) + ) +}; + +export default CardList; diff --git a/packages/2018/.toMove/src/components/CardList/ProjectCard.js b/packages/2018/.toMove/src/components/CardList/ProjectCard.js new file mode 100644 index 000000000..53da179be --- /dev/null +++ b/packages/2018/.toMove/src/components/CardList/ProjectCard.js @@ -0,0 +1,139 @@ +/** @jsx jsx */ +import { css, jsx } from "@emotion/core"; +import PropTypes from "prop-types"; +import { makeStyles } from "@material-ui/core/styles"; +import Card from "@material-ui/core/Card"; +import CardActions from "@material-ui/core/CardActions"; +import CardContent from "@material-ui/core/CardContent"; +import { Link } from "react-router"; +import { startCase } from "lodash"; + +const projectContentContainer = css` + position: relative; + &:focus-within { + box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.2), + 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 4px 2px -1px rgba(0, 0, 0, 0.12); + outline: 1px solid transparent; /* needed for Windows high-contrast mode */ + } +`; + +/* this is an accessibility feature to make the entire card clickable without wrapping all the content in a link. */ +const cardHeadlineLink = css` + display: inline-block; + color: unset; + &::before { + bottom: 0; + content: ""; + left: 0; + position: absolute; + right: 0; + top: 0; + } + &:focus-visible { + outline: 2px solid hsl(300, 5%, 55%); + } + &:-moz-focusring { + outline: 2px solid hsl(300, 5%, 55%); + } + &:hover { + color: currentColor; + } +`; + +const watermarkContainer = css` + position: absolute; + left: 0; + top: 0; + z-index: 0; + text-align: left; +`; + +const scaleCorner = css` + width: 5vw; + min-width: 67px; + max-width: 134px; + height: 5vw; + min-height: 67px; + max-height: 134px; +`; + +const nonInteractiveCta = css` + font-family: Rubik, Helvetica Neue, Helvetica, sans-serif; + font-size: 0.8125rem; + font-weight: 500; + line-height: 1.75; + padding: 0.307692308em 0.615384615em; + text-transform: uppercase; + transition: background-color 63ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, + box-shadow 63ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, + border 63ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; + z-index: 2; + &:hover { + background-color: rgba(32, 16, 36, 0.08); + cursor: pointer; + } +`; + +const useProjectStyles = makeStyles({ + card: { + "&:hover": { + boxShadow: + "0px 2px 6px 0px rgba(0,0,0,0.2), 0px 2px 2px 0px rgba(0,0,0,0.14), 0px 4px 2px -1px rgba(0,0,0,0.12)" + } + } +}); + +const Watermark = () => ( +
    + +
    +); + +const types = { + collection: { action: "View collection" }, + application: { action: "Check it out!" } +}; + +const ProjectCard = ({ title, description, link, type }) => { + const classes = useProjectStyles(); + + return ( + + +
    + + +

    + {startCase(type)}: {title} +

    +

    {description}

    +
    + + {types[type].action} + + +
    +
    + ); +}; + +ProjectCard.propTypes = { + title: PropTypes.string, + description: PropTypes.string, + link: PropTypes.string, + type: PropTypes.oneOf(["collection", "application"]) +}; + +export default ProjectCard; diff --git a/packages/2018/.toMove/src/components/CardList/cardListStyling.js b/packages/2018/.toMove/src/components/CardList/cardListStyling.js new file mode 100644 index 000000000..9648fd32e --- /dev/null +++ b/packages/2018/.toMove/src/components/CardList/cardListStyling.js @@ -0,0 +1,82 @@ +const drawerWidth = 240; +const headerHeight = 72; +const drawerGap = 0; +export default theme => ({ + root: { + display: "flex", + flexDirection: "column", + width: "95vw", + margin: "0px" + }, + drawer: { + [theme.breakpoints.up("sm")]: { + zIndex: "998", + width: drawerWidth, + flexShrink: 0 + } + }, + filtersButton: { + [theme.breakpoints.up("sm")]: { + display: "none" + } + }, + toolbar: { + width: "calc(100% - 10px)", + marginLeft: "10px" + }, + drawerPaper: { + width: drawerWidth, + [theme.breakpoints.up("sm")]: { + top: `calc(${headerHeight}px - 30px + ${drawerGap}px)`, + height: `calc(100vh - ${headerHeight}px + 30px - ${drawerGap}px)` + } + }, + filtersList: { + display: "flex", + flexDirection: "column", + justifyContent: "flex-start", + alignSelf: "center", + margin: "0px", + width: "100%" + }, + categoryListText: { + [theme.breakpoints.up("sm")]: { + flexGrow: 0 + } + }, + content: { + position: "absolute", + top: headerHeight, + padding: theme.spacing(1), + width: "100%", + [theme.breakpoints.up("sm")]: { + left: drawerWidth, + width: `calc(100% - ${drawerWidth}px)`, + alignSelf: "flex-end" + } + }, + filterItem: { + listStyleType: "none" + }, + entriesList: { + padding: "0px", + display: "flex", + flexWrap: "wrap" + }, + entry: { + margin: "10px", + flexWrap: "wrap", + listStyleType: "none", + alignSelf: "stretch", + width: "90%", + [theme.breakpoints.up("lg")]: { + width: "45%" + } + }, + nested: { + paddingLeft: theme.spacing(4) + }, + storyCard: { + height: "1000px" + } +}); diff --git a/packages/2018/.toMove/src/components/CardList/displayUtils.js b/packages/2018/.toMove/src/components/CardList/displayUtils.js new file mode 100644 index 000000000..ae4471535 --- /dev/null +++ b/packages/2018/.toMove/src/components/CardList/displayUtils.js @@ -0,0 +1,44 @@ +/* + function compares the tags on each story card to see if they match the + selected filter checkboxes. For a card to be shown it must have ONE tag that + matches a checkbox IN EACH CATEGORY. + + Example: If "Race", "Portland", and "Oregon" are active filters, cards will pass that contain + "Race" and either "Portland", or "Oregon". + + Passing Results: ["Race", "Portland"], ["Race", "Oregon"], ["Race", "Portland", "Oregon", "Banana"] +*/ + +export default ( + storyCardTags, + categories, + activeFilters, + showAllStories, + numberOfFiltersSelected +) => { + if (!storyCardTags) return false; + if (!storyCardTags.length) return false; + if (showAllStories) return true; + if (numberOfFiltersSelected === 0) return true; + + // flat array containing all categories that have filters selected + const targetCategories = categories.reduce((accumulator, category) => { + if (activeFilters[category].length > 0) { + return [...accumulator, category]; + } + return accumulator; + }, []); + + // returns false if there is no matching tag on a story card within any one target category + for (let i = 0; i < targetCategories.length; i += 1) { + let fulfillsTargetCategory = false; + activeFilters[targetCategories[i]].forEach(tag => { + if (storyCardTags.includes(tag)) { + fulfillsTargetCategory = true; + } + }); + if (!fulfillsTargetCategory) return false; + } + + return true; +}; diff --git a/packages/2018/.toMove/src/components/CivicCardStack/CivicCardStack.js b/packages/2018/.toMove/src/components/CivicCardStack/CivicCardStack.js new file mode 100644 index 000000000..df898110c --- /dev/null +++ b/packages/2018/.toMove/src/components/CivicCardStack/CivicCardStack.js @@ -0,0 +1,57 @@ +import PropTypes from "prop-types"; +import shortid from "shortid"; +/** @jsx jsx */ +import { jsx, css } from "@emotion/core"; +import { CivicStoryCard } from "@hackoregon/ui-cards"; + +const smallWatermark = ( + + + + + + +); + +const LAST_CARD_OPACITY = 0.4; + +const cardStackWrapper = total => css` + padding: 0 ${total * 5}px ${total * 5}px 0; +`; + +const bgCard = (index, total, baseOpacity) => css` + position: absolute; + z-index: ${total - index}; + left: ${5 * index}px; + top: ${-5 * index + 20}px; + opacity: ${1 - (index * (1 - baseOpacity)) / (total - 1)}; + padding-right: ${5 * (total - index - 1)}px; +`; + +const BackgroundStoryCard = (index, total, children) => ( +
    + + {children} + +
    +); + +const CivicCardStack = ({ cards, children }) => { + return ( +
    + {cards && + [...Array(cards)].map((item, index) => + BackgroundStoryCard(index, cards, children) + )} +
    + ); +}; + +CivicCardStack.propTypes = { + children: PropTypes.node, + cards: PropTypes.oneOf([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) +}; + +CivicCardStack.defaultProps = {}; + +export default CivicCardStack; diff --git a/packages/2018/.toMove/src/components/Footer/Footer.js b/packages/2018/.toMove/src/components/Footer/Footer.js new file mode 100644 index 000000000..375923ed4 --- /dev/null +++ b/packages/2018/.toMove/src/components/Footer/Footer.js @@ -0,0 +1,102 @@ +import PropTypes from "prop-types"; +import { Link } from "react-router"; +/** @jsx jsx */ +import { jsx, css } from "@emotion/core"; +import { Logo } from "@hackoregon/ui-brand"; +import ScrollToTop from "../ScrollToTop/ScrollToTop"; + +const commonFont = ` + font-family: 'Rubik'; + font-weight: 500; +`; + +const commonMargin = "12px 8px"; + +const footerClass = css` + width: 100%; + background: #fff; + display: flex; + align-items: center; + justify-content: center; + align-content: center; + flex-direction: row; + margin-left: 0; + margin-top: 200px; + padding: 32px 40px; + position: relative; + box-sizing: border-box; + border-top: 1px solid lightgrey; + + @media (min-width: 768px) { + margin-left: 0; + } + + @media (max-width: 850px) { + padding: 32px 20px; + } +`; + +const copyrightClass = css` + color: #726371; + ${commonFont} + margin: ${commonMargin}; +`; + +const logoClass = css` + flex: 2 auto; + display: flex; + align-items: center; + height: auto; + + & > img { + flex: 1; + padding: 12px; + } +`; + +const logoLinkStyle = css` + margin: 0 auto; + border-bottom: none; +`; + +const scrollToTopClass = css` + color: #EE495C; + ${commonFont} + margin: ${commonMargin}; + + & > div { + margin-left: auto; + } + + @media (min-width: 758px) { + margin-left: 0; + } +`; + +const defaultAttribution = `\u00A9 Copyright ${new Date().getFullYear()}`; + +const Footer = ({ attribution }) => ( +
    +
    {attribution}
    +
    + + + +
    +
    + +
    +
    +); + +Footer.displayName = "Footer"; + +Footer.defaultProps = { + attribution: defaultAttribution +}; + +Footer.propTypes = { + attribution: PropTypes.node +}; + +export default Footer; diff --git a/packages/2018/.toMove/src/components/FooterNew/FooterNew.js b/packages/2018/.toMove/src/components/FooterNew/FooterNew.js new file mode 100644 index 000000000..9bd4898f4 --- /dev/null +++ b/packages/2018/.toMove/src/components/FooterNew/FooterNew.js @@ -0,0 +1,111 @@ +/** @jsx jsx */ +import { jsx, css } from "@emotion/core"; +import { Logo } from "@hackoregon/ui-brand"; +import { BrandColors, browserDefaultSize } from "@hackoregon/ui-themes"; + +const footerWrapper = css` + height: 93px; + width: 100%; + background: #fff; + + position: relative; + box-sizing: border-box; +`; + +const contentWrapper = props => css` + margin: 0 auto; + max-width: ${props.greatestWidth}px; + padding: 27px 20px; + display: grid; + grid-template-columns: repeat(3, 1fr); + align-items: end; + + @media (max-width: ${props.condensedWidth}px) { + grid-template-columns: 1fr; + grid-template-rows: auto; + grid-row-gap: 30px; + } +`; + +const businessWrapper = props => css` + display: grid; + justify-content: center; + + @media (max-width: ${props.condensedWidth}px) { + justify-content: start; + padding: 0; + } +`; + +const logoStyle = props => css` + height: 90px !important; + justify-self: end; + + @media (max-width: ${props.condensedWidth}px) { + justify-self: start; + } +`; + +const textStyle = css` + color: ${BrandColors.primary.hex}; + font-family: Rubik, sans-serif; + font-weight: normal; + font-size: ${15 / browserDefaultSize}rem; + line-height: 150%; + text-decoration: none; + margin: 0; +`; + +const boldText = css` + ${textStyle}; + font-weight: 500; +`; + +const header = css` + ${textStyle}; + font-weight: 500; + font-size: ${20 / browserDefaultSize}rem; +`; + +const Footer = props => ( + +); + +Footer.defaultProps = { + greatestWidth: 1200, + condensedWidth: 580 +}; + +Footer.displayName = "Footer"; + +export default Footer; diff --git a/packages/2018/.toMove/src/components/FooterNew/FooterNew.test.js b/packages/2018/.toMove/src/components/FooterNew/FooterNew.test.js new file mode 100644 index 000000000..9328a22e8 --- /dev/null +++ b/packages/2018/.toMove/src/components/FooterNew/FooterNew.test.js @@ -0,0 +1,10 @@ +import React from "react"; +import { mount } from "enzyme"; +import FooterNew from "./FooterNew"; + +describe("FooterNew", () => { + it("should render FooterNew", () => { + const wrapper = mount(); + expect(wrapper.find("Footer")).to.have.length(1); + }); +}); diff --git a/packages/2018/.toMove/src/components/Header/FullNav.js b/packages/2018/.toMove/src/components/Header/FullNav.js new file mode 100644 index 000000000..0a6852f70 --- /dev/null +++ b/packages/2018/.toMove/src/components/Header/FullNav.js @@ -0,0 +1,322 @@ +/** @jsx jsx */ +import { css, jsx } from "@emotion/core"; +import { useState, useRef } from "react"; +import ClickAwayListener from "@material-ui/core/ClickAwayListener"; +import Grow from "@material-ui/core/Grow"; +import Paper from "@material-ui/core/Paper"; +import Popper from "@material-ui/core/Popper"; +import MenuList from "@material-ui/core/MenuList"; +import MenuItem from "@material-ui/core/MenuItem"; +import { Link } from "react-router"; +import { BrandColors, defaultFontSize } from "@hackoregon/ui-themes"; +import { Logo } from "@hackoregon/ui-brand"; +import navCaret from "../../assets/nav-caret.svg"; + +const { primary, action } = BrandColors; +const menuColor = "#F3F2F3"; + +const dropdownStyles = { + marginTop: "30px", + backgroundColor: menuColor +}; + +const optionText = { + fontFamily: "Rubik, sans-serif", + fontWeight: 500, + fontSize: `${16 / defaultFontSize}rem`, + lineHeight: `${37 / defaultFontSize}rem`, + color: primary.hex +}; + +const contentWrapper = props => css` + max-width: ${props.greatestWidth}px; + margin: 0 auto; + display: grid; + padding: 8px 20px 0; + align-items: center; + grid-template-columns: 80px 1fr 645px; +`; + +const logoStyle = css` + height: 60px !important; +`; + +const navStyle = css` + display: grid; + justify-self: end; +`; + +const linkContainer = css` + display: grid; + justify-self: end; + grid-template-columns: repeat(4, max-content); + grid-gap: ${48 / defaultFontSize}rem; + justify-items: end; + margin: 0; + padding: 0; +`; + +const listStyle = css` + display: unset; + text-align: unset; +`; + +const linkStyle = css` + font-weight: 500; + font-family: Rubik, sans-serif; + font-size: ${24 / defaultFontSize}rem; + line-height: ${28 / defaultFontSize}rem; + color: ${primary.hex}; + text-decoration: none; +`; + +const menuButton = css` + ${linkStyle}; + display: inline-grid; + text-align: end; + align-items: end; + background: none; + border: none; + margin: 0; + padding: 0; + color: inherit; + cursor: pointer; +`; + +const buttonText = css` + line-height: unset; + margin: 0; + + :hover { + color: ${action.hex}; + } +`; + +const caratStyle = css` + height: 8px; + margin-left: 5px; + padding-bottom: 2px; +`; + +const arrowUp = css` + position: absolute; + top: 15px; + right: 20px; + width: 0; + height: 0; + border-left: 15px solid transparent; + border-right: 15px solid transparent; + border-bottom: 15px solid ${menuColor}; +`; + +const menuLink = css` + color: ${primary.hex}; + text-decoration: none; +`; + +const FullNav = props => { + const [joinOpen, setJoinOpen] = useState(false); + const [aboutOpen, setAboutOpen] = useState(false); + const joinAnchorRef = useRef(null); + const aboutAnchorRef = useRef(null); + + function handleJoinToggle() { + setJoinOpen(prevOpen => !prevOpen); + } + + function handleAboutToggle() { + setAboutOpen(prevOpen => !prevOpen); + } + + function handleJoinClose(event) { + if (joinAnchorRef.current && joinAnchorRef.current.contains(event.target)) { + return; + } + setJoinOpen(false); + } + + function handleAboutClose(event) { + if ( + aboutAnchorRef.current && + aboutAnchorRef.current.contains(event.target) + ) { + return; + } + setAboutOpen(false); + } + + return ( +
    + + + +
    + +
    + ); +}; + +export default FullNav; diff --git a/packages/2018/.toMove/src/components/Header/Header.js b/packages/2018/.toMove/src/components/Header/Header.js new file mode 100644 index 000000000..82097623d --- /dev/null +++ b/packages/2018/.toMove/src/components/Header/Header.js @@ -0,0 +1,55 @@ +/** @jsx jsx */ +import { css, jsx } from "@emotion/core"; +import { useEffect, useState } from "react"; +import PropTypes from "prop-types"; +import FullNav from "./FullNav"; +import SmallNav from "./SmallNav"; + +const headerWrapper = css` + height: 72px; + width: 100vw; + position: fixed; + background: #fdfdfd; + box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.25); + margin: 0; + top: 0; + z-index: 999; +`; + +const Header = props => { + const { collapseWidth } = props; + const [width, setWidth] = useState(window.innerWidth); + + const handleResize = () => { + setWidth(window.innerWidth); + }; + + useEffect(() => { + window.addEventListener("resize", handleResize); + return () => { + window.removeEventListener("resize", handleResize); + }; + }); + + return ( +
    + {width > collapseWidth ? : } +
    + ); +}; + +Header.propTypes = { + greatestWidth: PropTypes.number, + collapseWidth: PropTypes.number, + condensedWidth: PropTypes.number +}; + +Header.defaultProps = { + greatestWidth: 1200, + collapseWidth: 845, + condensedWidth: 715 +}; + +Header.displayName = "Header"; + +export default Header; diff --git a/packages/2018/.toMove/src/components/Header/Header.test.js b/packages/2018/.toMove/src/components/Header/Header.test.js new file mode 100644 index 000000000..99ee4f476 --- /dev/null +++ b/packages/2018/.toMove/src/components/Header/Header.test.js @@ -0,0 +1,10 @@ +import React from "react"; +import { mount } from "enzyme"; +import Header from "./Header"; + +describe("Header", () => { + it("should render Header", () => { + const wrapper = mount(
    ); + expect(wrapper.find("Header")).to.have.length(1); + }); +}); diff --git a/packages/2018/.toMove/src/components/Header/SmallNav.js b/packages/2018/.toMove/src/components/Header/SmallNav.js new file mode 100644 index 000000000..2df751d65 --- /dev/null +++ b/packages/2018/.toMove/src/components/Header/SmallNav.js @@ -0,0 +1,207 @@ +/** @jsx jsx */ +import { css, jsx } from "@emotion/core"; +import { useState } from "react"; +import Drawer from "@material-ui/core/Drawer"; +import List from "@material-ui/core/List"; +import ListSubheader from "@material-ui/core/ListSubheader"; +import Divider from "@material-ui/core/Divider"; +import ListItem from "@material-ui/core/ListItem"; +import ListItemText from "@material-ui/core/ListItemText"; +import { Link } from "react-router"; +import { Logo } from "@hackoregon/ui-brand"; +import { BrandColors, defaultFontSize } from "@hackoregon/ui-themes"; + +import hamburgerMenu from "../../assets/burger.svg"; + +const { primary, secondary } = BrandColors; + +const contentWrapper = css` + margin: 0 auto; + display: grid; + padding: 16px 20px 12px; + align-items: center; + justify-content: space-between; + grid-template-columns: repeat(3, 1fr); +`; + +const logoWrapper = css` + justify-self: center; +`; + +const logoStyle = css` + height: 45px !important; +`; + +// Padding makes it easier to click +const hamburgerStyle = css` + background: none; + border: none; + margin: 0; + padding: 0; + cursor: pointer; + padding: 10px 10px 10px 0; + + :focus { + outline: none; + } +`; + +const linkStyle = css` + font-weight: 500; + font-family: Rubik, sans-serif; + font-size: ${24 / defaultFontSize}rem; + line-height: ${28 / defaultFontSize}rem; + color: ${primary.hex}; + text-decoration: none; +`; + +const subHeaderStyle = css` + ${linkStyle}; + color: ${secondary.hex}; + padding-top: 8px; + margin: 0; +`; + +const subLinkStyle = css` + ${linkStyle}; + padding-left: 20px; +`; + +const SmallNav = () => { + const [open, setOpen] = useState(false); + + const toggleDrawer = isOpen => event => { + if ( + event.type === "keydown" && + (event.key === "Tab" || event.key === "Shift") + ) { + return; + } + + setOpen(isOpen); + }; + + const fullList = () => ( +
    +
    +
    + +
    + + + +
    +
    + + + + + + + + + + + +

    JOIN THE MOVEMENT

    +
    + + + + + + + + + + + + + + +

    ABOUT

    +
    + + + + + + + + + + + + + + + + + + +
    +
    + ); + + return ( + + ); +}; + +export default SmallNav; diff --git a/packages/2018/.toMove/src/components/Hero/CollectionHero.js b/packages/2018/.toMove/src/components/Hero/CollectionHero.js new file mode 100644 index 000000000..0e22047e8 --- /dev/null +++ b/packages/2018/.toMove/src/components/Hero/CollectionHero.js @@ -0,0 +1,41 @@ +import PropTypes from "prop-types"; +/** @jsx jsx */ +import { jsx, css } from "@emotion/core"; +import Hero from "./Hero"; + +const teamTitleStyle = css` + display: block; + font-size: 13px; + font-family: "Rubik", sans-serif; + text-transform: uppercase; + letter-spacing: 3px; +`; + +const titleStyle = css` + font-size: 50px; + line-height: 1.2; + font-weight: 300; + font-family: "Rubik", sans-serif; + margin-bottom: 12px; + @media (max-width: 640px) { + font-size: 36px; + } +`; + +const CollectionHero = ({ heroTitle, heroSubtitle, teamTitle }) => ( + +
    + {teamTitle} +

    {heroTitle}

    +

    {heroSubtitle}

    +
    +
    +); + +CollectionHero.propTypes = { + teamTitle: PropTypes.string, + heroTitle: PropTypes.string, + heroSubtitle: PropTypes.string +}; + +export default CollectionHero; diff --git a/packages/2018/.toMove/src/components/Hero/Hero.js b/packages/2018/.toMove/src/components/Hero/Hero.js new file mode 100644 index 000000000..971c54ebc --- /dev/null +++ b/packages/2018/.toMove/src/components/Hero/Hero.js @@ -0,0 +1,61 @@ +import PropTypes from "prop-types"; +/** @jsx jsx */ +import { jsx, css } from "@emotion/core"; +import { BrandColors } from "@hackoregon/ui-themes"; + +const heroClass = css` + display: flex; + background: rgb(34, 15, 37); + height: 75vh; + min-height: 600px; + width: 100%; + margin: 0 0 120px; + padding: 0; + background-size: cover; + background-position: center center; + z-index: -100; + align-items: center; + justify-content: center; +`; + +const containerClass = css` + display: flex; + width: 100%; + max-width: 800px; + + @media (max-width: 640px) { + padding: 0 15px; + } +`; + +const contentClass = css` + position: relative; + padding-top: 0px; + display: flex; + align-items: center; + color: #fff; +`; + +const DefaultChildren = () => ( +

    + Data for the people, +
    + by the people. +

    +); + +const Hero = ({ children }) => ( +
    +
    +
    {children || }
    +
    +
    +); + +Hero.displayName = "Hero"; + +Hero.propTypes = { + children: PropTypes.node +}; + +export default Hero; diff --git a/packages/2018/.toMove/src/components/Icon/Icon.js b/packages/2018/.toMove/src/components/Icon/Icon.js new file mode 100644 index 000000000..b1fd38ee5 --- /dev/null +++ b/packages/2018/.toMove/src/components/Icon/Icon.js @@ -0,0 +1,15 @@ +import PropTypes from "prop-types"; +import React from "react"; + +const Icon = ({ className, handleClick }) => ( + +