Entry Page Updates (#1130)
* added glyphs * updated other assets * initial style updates * initial detailed card updates * fixed mobile spacing * final desktop layout updates * made btn-tag smaller on moderation entry * layout updates * testing bookmark feature * go away button with no link * optimized svgs * moderation updates * accidentally deleted bookmark icon * style, a11y tweaks * type & bug fixes * pre-code review updates * header padding fix * updates * a11y removal * more updates * let's see if this fixes it * style updates * Update components/project-card/project-card-detailed.scss Co-Authored-By: Mavis Ou <mmmavis@users.noreply.github.com>
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="14" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14">
|
||||
<path d="M12.57 8.57v2.86c0 .707-.25 1.313-.753 1.816A2.478 2.478 0 0 1 10 14H2.57c-.707 0-1.313-.25-1.816-.754A2.474 2.474 0 0 1 0 11.43V4c0-.708.25-1.314.754-1.817a2.478 2.478 0 0 1 1.817-.754h6.29c.087 0 .156.02.21.08.053.05.08.12.08.2v.57c0 .083-.027.15-.08.205a.279.279 0 0 1-.206.08H2.57c-.39 0-.728.14-1.007.42-.28.28-.42.616-.42 1.01v7.43c0 .39.14.727.42 1.007.28.28.616.42 1.008.42H10c.393 0 .73-.14 1.01-.42.28-.28.42-.617.42-1.01v-2.85a.274.274 0 0 1 .284-.285h.572c.083 0 .152.026.205.08.06.054.08.122.08.205zM16 .858V5.43a.549.549 0 0 1-.17.4.549.549 0 0 1-.4.17.55.55 0 0 1-.403-.17l-1.572-1.57-5.82 5.82a.286.286 0 0 1-.416 0L6.202 9.062a.28.28 0 0 1-.09-.205.28.28 0 0 1 .09-.205l5.822-5.822-1.572-1.57a.554.554 0 0 1-.17-.403c0-.155.057-.29.17-.402a.55.55 0 0 1 .402-.17h4.572c.15 0 .284.057.4.17.11.113.17.247.17.402z" fill="#fff" fill-rule="evenodd"/>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 966 B |
|
@ -1 +1,3 @@
|
|||
<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m20 4-16 16m0-16 16 16" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/></svg>
|
||||
<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 4L4 20M4 4l16 16" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
||||
</svg>
|
||||
|
|
До Ширина: | Высота: | Размер: 193 B После Ширина: | Высота: | Размер: 198 B |
|
@ -0,0 +1,6 @@
|
|||
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(1 1)" fill="none" fill-rule="evenodd">
|
||||
<circle stroke="#0d10bf" stroke-width="2" cx="18.5" cy="18.5" r="18.5"/>
|
||||
<path d="M27.262 15.57c0-.198 0-.394-.014-.589a9.205 9.205 0 0 0 2.252-2.353 8.94 8.94 0 0 1-2.592.716 4.56 4.56 0 0 0 1.985-2.515 9.008 9.008 0 0 1-2.867 1.104 4.496 4.496 0 0 0-6.386-.198 4.572 4.572 0 0 0-1.306 4.344A12.79 12.79 0 0 1 9.03 11.33a4.568 4.568 0 0 0 1.398 6.067 4.456 4.456 0 0 1-2.049-.568v.058A4.54 4.54 0 0 0 12 21.342a4.46 4.46 0 0 1-2.037.078 4.522 4.522 0 0 0 4.216 3.157 9.015 9.015 0 0 1-6.68 1.885 12.717 12.717 0 0 0 6.919 2.038c8.302.003 12.842-6.924 12.842-12.93z" fill="#0d10bf"/>
|
||||
</g>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 747 B |
|
@ -0,0 +1,6 @@
|
|||
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(1 1)" fill="none" fill-rule="evenodd">
|
||||
<circle stroke="#000" stroke-width="2" cx="18.5" cy="18.5" r="18.5"/>
|
||||
<path d="M27.262 15.57c0-.198 0-.394-.014-.589a9.205 9.205 0 0 0 2.252-2.353 8.94 8.94 0 0 1-2.592.716 4.56 4.56 0 0 0 1.985-2.515 9.008 9.008 0 0 1-2.867 1.104 4.496 4.496 0 0 0-6.386-.198 4.572 4.572 0 0 0-1.306 4.344A12.79 12.79 0 0 1 9.03 11.33a4.568 4.568 0 0 0 1.398 6.067 4.456 4.456 0 0 1-2.049-.568v.058A4.54 4.54 0 0 0 12 21.342a4.46 4.46 0 0 1-2.037.078 4.522 4.522 0 0 0 4.216 3.157 9.015 9.015 0 0 1-6.68 1.885 12.717 12.717 0 0 0 6.919 2.038c8.302.003 12.842-6.924 12.842-12.93z" fill="#000"/>
|
||||
</g>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 741 B |
Двоичные данные
assets/placeholder-thumbnail.png
До Ширина: | Высота: | Размер: 344 KiB После Ширина: | Высота: | Размер: 207 KiB |
|
@ -1 +1,3 @@
|
|||
<svg width="15" height="10" xmlns="http://www.w3.org/2000/svg"><path d="M1.354 2.01l5.52 5.52m.49.49l6.01-6.01" stroke="#000" stroke-width="2" fill="none" fill-rule="evenodd" stroke-linecap="square"/></svg>
|
||||
<svg width="15" height="10" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.354 2.01l5.52 5.52m.49.49l6.01-6.01" stroke="#000" stroke-width="2" fill="none" stroke-linecap="square"/>
|
||||
</svg>
|
||||
|
|
До Ширина: | Высота: | Размер: 207 B После Ширина: | Высота: | Размер: 193 B |
|
@ -1,3 +1,3 @@
|
|||
<svg width="12" height="12" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
|
||||
<path d="M7.4 3.95l2.85.416-2.063 2.01.487 2.837-2.549-1.34-2.55 1.34.488-2.838L2 4.366l2.85-.416 1.275-2.583z" stroke="#FFF" stroke-width="1.5" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<svg width="12" height="12" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.4 3.95l2.85.416-2.063 2.01.487 2.837-2.549-1.34-2.55 1.34.488-2.838L2 4.366l2.85-.416 1.275-2.583z" stroke="#FFF" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
|
До Ширина: | Высота: | Размер: 319 B После Ширина: | Высота: | Размер: 279 B |
|
@ -0,0 +1,6 @@
|
|||
<svg width="37" height="37" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
|
||||
<g transform="translate(1 1)" fill="none" fill-rule="evenodd" stroke="#000" stroke-width="2">
|
||||
<circle cx="18.5" cy="18.5" r="18.5"/>
|
||||
<path d="M27.804 11.602a5.789 5.789 0 0 0-8.189 0L18.5 12.718l-1.116-1.116a5.79 5.79 0 0 0-8.188 8.188l1.115 1.116 8.189 8.188 8.188-8.188 1.116-1.116a5.789 5.789 0 0 0 0-8.188z" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 481 B |
|
@ -0,0 +1,6 @@
|
|||
<svg width="37" height="37" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
|
||||
<g transform="translate(1 1)" fill="none" fill-rule="evenodd" stroke="#000" stroke-width="2">
|
||||
<circle cx="18.5" cy="18.5" r="18.5"/>
|
||||
<path d="M27.804 11.602a5.789 5.789 0 0 0-8.189 0L18.5 12.718l-1.116-1.116a5.79 5.79 0 0 0-8.188 8.188l1.115 1.116 8.189 8.188 8.188-8.188 1.116-1.116a5.789 5.789 0 0 0 0-8.188z" fill="#ff4f5e" stroke="#ff4f5e" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 513 B |
|
@ -1,3 +1,3 @@
|
|||
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21.304 4.102a5.789 5.789 0 0 0-8.189 0L12 5.218l-1.116-1.116a5.79 5.79 0 0 0-8.188 8.188l1.115 1.116L12 21.594l8.188-8.188 1.116-1.116a5.789 5.789 0 0 0 0-8.188z" stroke="#999" stroke-width="2" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21.304 4.102a5.789 5.789 0 0 0-8.189 0L12 5.218l-1.116-1.116a5.79 5.79 0 0 0-8.188 8.188l1.115 1.116L12 21.594l8.188-8.188 1.116-1.116a5.789 5.789 0 0 0 0-8.188z" stroke="#999" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
|
До Ширина: | Высота: | Размер: 358 B После Ширина: | Высота: | Размер: 338 B |
|
@ -4,7 +4,6 @@ import PropTypes from "prop-types";
|
|||
import bookmarkManager from "../js/bookmarks-manager";
|
||||
import Service from "../js/service.js";
|
||||
import user from "../js/app-user.js";
|
||||
import classNames from "classnames";
|
||||
|
||||
class BookmarkControl extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -122,11 +121,9 @@ class BookmarkControl extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
let classnames = classNames(`heart`, this.props.className);
|
||||
|
||||
return (
|
||||
<a
|
||||
className={classnames}
|
||||
className={this.props.className}
|
||||
ref="heart"
|
||||
onClick={() => this.handleBookmarkClick()}
|
||||
/>
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Link } from "react-router-dom";
|
||||
import classNames from "classnames";
|
||||
|
||||
const Creators = props => {
|
||||
if (props.creators.length === 0) return null;
|
||||
|
||||
let classnames = classNames(`my-1 body-small creator`, props.className);
|
||||
let labelText = props.showLabelText ? `Created by ` : ``;
|
||||
let creators = [];
|
||||
|
||||
|
@ -39,7 +37,7 @@ const Creators = props => {
|
|||
}
|
||||
|
||||
return (
|
||||
<p className={classnames}>
|
||||
<p>
|
||||
{labelText}
|
||||
{creators}
|
||||
</p>
|
||||
|
|
|
@ -6,13 +6,17 @@ const Description = props => {
|
|||
let paragraphs = props.description.split(`\n`).map(paragraph => {
|
||||
if (!paragraph) return null;
|
||||
|
||||
return <p key={paragraph}>{paragraph}</p>;
|
||||
return (
|
||||
<p key={paragraph} className="body-large">
|
||||
{paragraph}
|
||||
</p>
|
||||
);
|
||||
});
|
||||
|
||||
if (paragraphs.length < 1) return null;
|
||||
|
||||
return (
|
||||
<div className={classNames(`description`, props.className)}>
|
||||
<div className={classNames(`description mt-3`, props.className)}>
|
||||
{paragraphs}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Link } from "react-router-dom";
|
||||
import classNames from "classnames";
|
||||
|
||||
const DEFAULT_TEXT = `Help with this, or find other projects that have similar ways to get involved.`;
|
||||
|
||||
|
@ -20,40 +21,53 @@ class GetInvolved extends React.Component {
|
|||
let props = this.props;
|
||||
let getInvolvedText = props.getInvolved ? props.getInvolved : null;
|
||||
let getInvolvedLink = props.getInvolvedUrl ? (
|
||||
<p className="mt-2">
|
||||
<strong>
|
||||
<a
|
||||
href={props.getInvolvedUrl}
|
||||
target="_blank"
|
||||
onClick={() => this.handleGetInvolvedLinkClick()}
|
||||
>
|
||||
Get Involved
|
||||
{props.getInvolvedUrl}
|
||||
</a>
|
||||
</strong>
|
||||
</p>
|
||||
) : null;
|
||||
if (!getInvolvedText && !getInvolvedLink) return <p>{DEFAULT_TEXT}</p>;
|
||||
|
||||
return (
|
||||
<p>
|
||||
{getInvolvedText} {getInvolvedLink}
|
||||
</p>
|
||||
<div>
|
||||
<p>{getInvolvedText}</p>
|
||||
{getInvolvedLink}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderHelpLabels() {
|
||||
if (!this.props.helpTypes) return null;
|
||||
|
||||
return this.props.helpTypes.map(helpType => {
|
||||
let classes = classNames(`btn btn-tag`, {
|
||||
"btn-xs": this.props.onModerationMode
|
||||
});
|
||||
|
||||
let helpLabels = this.props.helpTypes.map(helpType => {
|
||||
return (
|
||||
<Link
|
||||
to={`/projects?helpType=${encodeURIComponent(helpType)}`}
|
||||
className="btn btn-xs btn-tag"
|
||||
className={classes}
|
||||
key={helpType}
|
||||
>
|
||||
{helpType}
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
|
||||
return <div className="mt-4">{helpLabels}</div>;
|
||||
}
|
||||
|
||||
render() {
|
||||
let classnames = classNames(`help-needed`, this.props.className);
|
||||
|
||||
if (
|
||||
!this.props.getInvolved &&
|
||||
!this.props.getInvolvedUrl &&
|
||||
|
@ -62,8 +76,8 @@ class GetInvolved extends React.Component {
|
|||
return null;
|
||||
|
||||
return (
|
||||
<div className="get-involved pb-3 mb-3">
|
||||
<h2>Help needed</h2>
|
||||
<div className={classnames}>
|
||||
<h3>Help needed</h3>
|
||||
{this.renderGetInvolvedText()}
|
||||
{this.renderHelpLabels()}
|
||||
</div>
|
||||
|
|
|
@ -20,10 +20,7 @@ const IssuesAndTags = props => {
|
|||
|
||||
let tags = props.tags.map(tag => {
|
||||
return (
|
||||
<Link
|
||||
to={`/tags/${encodeURIComponent(tag)}`}
|
||||
key={tag}
|
||||
>
|
||||
<Link to={`/tags/${encodeURIComponent(tag)}`} key={tag}>
|
||||
#{tag}
|
||||
</Link>
|
||||
);
|
||||
|
@ -34,11 +31,7 @@ const IssuesAndTags = props => {
|
|||
|
||||
issues_and_tags = issues_and_tags.reduce((accu, curr) => [accu, ", ", curr]);
|
||||
|
||||
return (
|
||||
<div className={classnames}>
|
||||
{issues_and_tags}
|
||||
</div>
|
||||
);
|
||||
return <div className={classnames}>{issues_and_tags}</div>;
|
||||
};
|
||||
|
||||
IssuesAndTags.propTypes = {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Link } from "react-router-dom";
|
||||
import classNames from "classnames";
|
||||
|
||||
class Thumbnail extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -19,9 +20,10 @@ class Thumbnail extends React.Component {
|
|||
<div
|
||||
className="img-container"
|
||||
style={{ backgroundImage: `url(${imgSrc})` }}
|
||||
role="presentation"
|
||||
/>
|
||||
);
|
||||
let classnames = `thumbnail`;
|
||||
let classnames = classNames(`thumbnail`, this.props.className);
|
||||
|
||||
if (this.props.link) {
|
||||
return (
|
||||
|
|
|
@ -13,9 +13,11 @@ class Title extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
let classnames = classNames(`title mb-0 h4-heading`, this.props.className);
|
||||
let title = this.props.title;
|
||||
let simpleView = this.props.simpleView;
|
||||
let content = <h1 className="title h1-heading mb-1 mb-md-3">{title}</h1>;
|
||||
|
||||
if (simpleView) {
|
||||
if (this.props.link) {
|
||||
title = (
|
||||
<Link to={this.props.link} onClick={() => this.handleTitleClick()}>
|
||||
|
@ -24,7 +26,10 @@ class Title extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
return <h4 className={classnames}>{title}</h4>;
|
||||
content = <h4 className="title h4-heading mb-0">{title}</h4>;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import PropTypes from "prop-types";
|
|||
const WhyInteresting = props => {
|
||||
if (!props.interest) return null;
|
||||
|
||||
return <p className="interest">{props.interest}</p>;
|
||||
return <p className="interest body-large mt-3">{props.interest}</p>;
|
||||
};
|
||||
|
||||
WhyInteresting.propTypes = {
|
||||
|
|
|
@ -40,6 +40,7 @@ class DetailedProjectCard extends React.Component {
|
|||
|
||||
componentDidMount() {
|
||||
this.setInitialBookmarkedStatus();
|
||||
this.setSocialPanelHeight();
|
||||
}
|
||||
|
||||
setInitialBookmarkedStatus() {
|
||||
|
@ -91,6 +92,35 @@ class DetailedProjectCard extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
// Sets height for social icons grid cell to match the height of entry content
|
||||
setSocialPanelHeight() {
|
||||
let article = this.article; //entry content
|
||||
let socialIcons = this.socialIcons; //social icons
|
||||
|
||||
// We want to set the social icons height at large+ devices
|
||||
let breakpoint = window.matchMedia("(min-width: 992px)");
|
||||
|
||||
function renderHeight(breakpoint) {
|
||||
// If screen size is at large+ breakpoint
|
||||
if (breakpoint.matches) {
|
||||
// Set the height of the social icons grid cell to the height of loaded entry
|
||||
socialIcons.style.height = `${article.offsetHeight}px`;
|
||||
|
||||
// Since social icons is in a CSS Grid cell, the cell is now the height of the entry &
|
||||
// the entry has been pushed below the grid; so, we want to offset the social icons height with
|
||||
// a negative margin-top on the entry (article) to bring the content back up.
|
||||
article.style.marginTop = `calc(0px - (${article.offsetHeight}px + 80px))`;
|
||||
} else {
|
||||
// If not at large+ breakpoint we want these values to be 'auto'
|
||||
socialIcons.style.height = `auto`;
|
||||
article.style.marginTop = `auto`;
|
||||
}
|
||||
}
|
||||
|
||||
renderHeight(breakpoint); // Calls our function at run time
|
||||
breakpoint.addListener(renderHeight); // Runs our callback function in response to any media query status changes
|
||||
}
|
||||
|
||||
renderVisitButton() {
|
||||
if (!this.props.contentUrl) return null;
|
||||
|
||||
|
@ -98,7 +128,7 @@ class DetailedProjectCard extends React.Component {
|
|||
<a
|
||||
href={this.props.contentUrl}
|
||||
target="_blank"
|
||||
className="btn btn-block btn-info btn-visit text-capitalize mt-3"
|
||||
className="btn btn-primary mb-3 mb-md-0 mr-md-3 d-flex justify-content-center align-items-center"
|
||||
onClick={() => this.handleVisitBtnClick()}
|
||||
>
|
||||
Visit
|
||||
|
@ -106,6 +136,20 @@ class DetailedProjectCard extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
renderGetInvolvedButton() {
|
||||
if (!this.props.getInvolvedUrl) return null;
|
||||
|
||||
return (
|
||||
<a
|
||||
href={this.props.getInvolvedUrl}
|
||||
target="_blank"
|
||||
className="btn btn-secondary d-flex justify-content-center"
|
||||
>
|
||||
Get Involved
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
renderTimePosted() {
|
||||
if (!this.props.created && !this.props.publishedBy) return null;
|
||||
|
||||
|
@ -128,25 +172,29 @@ class DetailedProjectCard extends React.Component {
|
|||
) : null;
|
||||
|
||||
return (
|
||||
<p>
|
||||
<small className="time-posted d-block">
|
||||
<p className="time-posted mb-4">
|
||||
Added{timePosted}
|
||||
{publishedBy}
|
||||
</small>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
renderActionPanel() {
|
||||
renderSocialPanel() {
|
||||
let twitterUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(
|
||||
this.props.title
|
||||
)}&url=${encodeURIComponent(window.location.href)}`;
|
||||
|
||||
return (
|
||||
<div className="action-panel pb-3 mb-3">
|
||||
<div className="d-flex share">
|
||||
<aside
|
||||
className="social-panel-wrapper mb-4 pl-md-3 pb-xl-4"
|
||||
ref={socialIcons => {
|
||||
this.socialIcons = socialIcons;
|
||||
}}
|
||||
>
|
||||
<div className="social-panel">
|
||||
<BookmarkControl
|
||||
id={this.props.id}
|
||||
className="circle-heart large mr-3 mr-lg-0 mb-lg-2"
|
||||
title={this.props.title}
|
||||
isBookmarked={this.props.isBookmarked}
|
||||
updateCardBookmarkedState={bookmarked => {
|
||||
|
@ -156,53 +204,73 @@ class DetailedProjectCard extends React.Component {
|
|||
<a
|
||||
href={twitterUrl}
|
||||
onClick={evt => this.handleTwitterShareClick(evt)}
|
||||
className="btn twitter-share d-inline-block align-self-center mx-3"
|
||||
className="circle-twitter large"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
renderTopHeader() {
|
||||
renderTitleAuthor() {
|
||||
return (
|
||||
<div className="col-12 mb-3">
|
||||
<div className="row">
|
||||
<div className="col-12 col-sm-8">
|
||||
<Title title={this.props.title} className="mb-1" />
|
||||
<div className="title-author-wrapper mb-4 mb-md-5 mb-lg-0">
|
||||
<div className="title-author">
|
||||
<header className="mb-md-4">
|
||||
<Title title={this.props.title} />
|
||||
<Creators
|
||||
creators={this.props.relatedCreators}
|
||||
showLabelText={true}
|
||||
creatorClickHandler={(event, name) =>
|
||||
this.handleCreatorClick(event, name)
|
||||
}
|
||||
className="mb-4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderLeftColumn() {
|
||||
let wrapperClassnames = classNames(`col-12 col-md-8`);
|
||||
|
||||
return (
|
||||
<div className={wrapperClassnames}>
|
||||
<Thumbnail thumbnail={this.props.thumbnail} />
|
||||
</header>
|
||||
<div className="action-panel d-flex flex-column flex-md-row">
|
||||
{this.renderVisitButton()}
|
||||
<Description description={this.props.description} className="mt-3" />
|
||||
<WhyInteresting interest={this.props.interest} />
|
||||
{this.renderTimePosted()}
|
||||
<IssuesAndTags issues={this.props.issues} tags={this.props.tags} />
|
||||
{this.renderGetInvolvedButton()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderRightColumn() {
|
||||
let wrapperClassnames = classNames(`col-12 col-md-4 mt-3 mt-md-0`);
|
||||
renderThumbnail() {
|
||||
return (
|
||||
<div className="thumbnail-wrapper mb-4 mb-md-0">
|
||||
<div className="thumbnail-container">
|
||||
<Thumbnail thumbnail={this.props.thumbnail} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderMainContent() {
|
||||
let getInvolved = classNames("get-involved w-100 mb-5", {
|
||||
"d-none":
|
||||
!this.props.getInvolved &&
|
||||
!this.props.getInvolvedUrl &&
|
||||
!this.props.helpTypes.length > 0
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={wrapperClassnames}>
|
||||
{this.renderActionPanel()}
|
||||
<article
|
||||
className="main-content"
|
||||
ref={article => {
|
||||
this.article = article;
|
||||
}}
|
||||
>
|
||||
<section className="summary-info mb-5">
|
||||
<div className="container">
|
||||
<div className="offset-lg-2">
|
||||
<Description description={this.props.description} />
|
||||
<WhyInteresting interest={this.props.interest} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className={getInvolved}>
|
||||
<div className="container">
|
||||
<div className="offset-lg-2 py-5">
|
||||
<GetInvolved
|
||||
getInvolved={this.props.getInvolved}
|
||||
getInvolvedUrl={this.props.getInvolvedUrl}
|
||||
|
@ -210,32 +278,50 @@ class DetailedProjectCard extends React.Component {
|
|||
sendGaEvent={config => this.sendGaEvent(config)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="issues-and-tags-container mb-4 mb-md-5">
|
||||
<div className="container">
|
||||
<div className="offset-lg-2">
|
||||
<IssuesAndTags
|
||||
issues={this.props.issues}
|
||||
tags={this.props.tags}
|
||||
className="mb-4 mb-md-5"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div className="container">
|
||||
<div className="offset-lg-2">
|
||||
{this.renderTimePosted()}
|
||||
<p className="report-correction">
|
||||
Correction?{" "}
|
||||
<a href="https://mzl.la/pulse-contact">Contact us</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let wrapperClassnames = classNames(`col-12 pt-3 project-card detail-view`, {
|
||||
let wrapperClassnames = classNames(`project-card detail-view`, {
|
||||
bookmarked: this.state.bookmarked
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={wrapperClassnames}>
|
||||
<div className="row">{this.renderTopHeader()}</div>
|
||||
<div className="row">
|
||||
{this.renderLeftColumn()}
|
||||
{this.renderRightColumn()}
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-12 col-md-8">
|
||||
<p className="report-correction mt-md-3 pt-md-3">
|
||||
<small>
|
||||
Correction?{" "}
|
||||
<a href="https://mzl.la/pulse-contact">Contact us</a>.
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
<main className={wrapperClassnames}>
|
||||
<div className="thumbnail-title-social-wrapper mb-4 mb-md-5">
|
||||
<div className="thumbnail-title-social">
|
||||
{this.renderThumbnail()}
|
||||
{this.renderTitleAuthor()}
|
||||
{this.renderSocialPanel()}
|
||||
</div>
|
||||
</div>
|
||||
{this.renderMainContent()}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
.project-card.detail-view {
|
||||
background: inherit;
|
||||
|
||||
.circle-heart {
|
||||
display: inline-block;
|
||||
width: 37px;
|
||||
height: 37px;
|
||||
background-image: url(/assets/svg/icon-bookmark-entry-detailed.svg);
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
|
||||
&.beating {
|
||||
animation-name: beating;
|
||||
animation-duration: 0.4s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
}
|
||||
|
||||
&.bookmarked .circle-heart {
|
||||
background-image: url(/assets/svg/icon-bookmark-selected-entry-detailed.svg);
|
||||
}
|
||||
|
||||
.issues-and-tags-container {
|
||||
@include scaleText(16px, 24px, 20px, 28px);
|
||||
|
||||
color: $dark-blue;
|
||||
|
||||
@media (min-width: $bp-md) {
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
|
||||
.get-involved {
|
||||
background-color: $gray-05;
|
||||
}
|
||||
|
||||
.description {
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.thumbnail-wrapper {
|
||||
grid-area: image;
|
||||
|
||||
@media (min-width: $bp-md) {
|
||||
grid-column: 1 / 2;
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
@media (min-width: $bp-lg) {
|
||||
grid-column: 1 / 13;
|
||||
grid-row: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.title-author-wrapper {
|
||||
grid-area: info;
|
||||
|
||||
@media (min-width: $bp-lg) {
|
||||
grid-column: 10 / 25;
|
||||
grid-row: 1;
|
||||
z-index: 1;
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.social-panel-wrapper {
|
||||
grid-area: icons;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.thumbnail-title-social {
|
||||
display: grid;
|
||||
grid-template:
|
||||
[row1-start] "image image image" 1fr [row1-end]
|
||||
[row2-start] ". info ." 0.5fr [row2-end]
|
||||
[row3-start] ". icons ." 40px [row3-end]
|
||||
/ 15px 1fr 15px;
|
||||
|
||||
@media (min-width: $bp-sm) {
|
||||
grid-template-columns: 100px 1fr 100px;
|
||||
}
|
||||
|
||||
@media (min-width: $bp-md) {
|
||||
margin-top: 48px;
|
||||
grid-template:
|
||||
[row1-start] "info info" 0.5fr [row1-end]
|
||||
[row2-start] "image icons" 1.5fr [row2-end]
|
||||
/ 2fr 1fr;
|
||||
}
|
||||
|
||||
@media (min-width: $bp-lg) {
|
||||
grid-template-columns: repeat(24, 1fr);
|
||||
grid-template-rows: 0.25fr 0.25fr;
|
||||
grid-row-gap: 48px;
|
||||
}
|
||||
|
||||
&-wrapper {
|
||||
@media (min-width: $bp-md) {
|
||||
width: map-get($container-max-widths, md);
|
||||
max-width: 100%;
|
||||
padding: 0 15px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@media (min-width: $bp-lg) {
|
||||
width: map-get($container-max-widths, lg);
|
||||
}
|
||||
|
||||
@media (min-width: $bp-xl) {
|
||||
width: map-get($container-max-widths, xl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title-author {
|
||||
@media (min-width: $bp-lg) {
|
||||
background-color: $white;
|
||||
padding: 24px 0 24px 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.social-panel {
|
||||
@media (min-width: $bp-lg) {
|
||||
position: sticky;
|
||||
top: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-panel {
|
||||
@media (min-width: $bp-lg) {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
a {
|
||||
flex: 1;
|
||||
|
||||
@media (min-width: $bp-md) {
|
||||
flex: unset;
|
||||
}
|
||||
|
||||
@media (min-width: $bp-lg) {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.description,
|
||||
.interest,
|
||||
.help-needed,
|
||||
.issues-and-tags,
|
||||
.time-posted,
|
||||
.report-correction {
|
||||
@media (min-width: $bp-lg) {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -86,7 +86,7 @@ class ProjectCard extends React.Component {
|
|||
updateCardBookmarkedState={bookmarked => {
|
||||
this.updateCardBookmarkedState(bookmarked);
|
||||
}}
|
||||
className="align-middle"
|
||||
className="heart align-middle"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -109,16 +109,14 @@ class ProjectCard extends React.Component {
|
|||
return (
|
||||
<div>
|
||||
<WhyInteresting interest={this.props.interest} />
|
||||
<IssuesAndTags
|
||||
issues={this.props.issues}
|
||||
tags={this.props.tags}
|
||||
className={`pb-3 mb-3`}
|
||||
/>
|
||||
<IssuesAndTags issues={this.props.issues} tags={this.props.tags} />
|
||||
<GetInvolved
|
||||
getInvolved={this.props.getInvolved}
|
||||
getInvolvedUrl={this.props.getInvolvedUrl}
|
||||
helpTypes={this.props.helpTypes}
|
||||
sendGaEvent={config => this.sendGaEvent(config)}
|
||||
onModeration={true}
|
||||
className="py-3 my-4"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -180,6 +178,7 @@ class ProjectCard extends React.Component {
|
|||
thumbnail={this.props.thumbnail}
|
||||
link={!this.props.onModerationMode ? detailViewLink : ``}
|
||||
sendGaEvent={() => this.handleReadMoreClick()}
|
||||
className="simple-view"
|
||||
/>
|
||||
<div className="content mt-2">
|
||||
<div className="d-flex">
|
||||
|
@ -187,7 +186,7 @@ class ProjectCard extends React.Component {
|
|||
title={this.props.title}
|
||||
link={!this.props.onModerationMode ? detailViewLink : ``}
|
||||
sendGaEvent={() => this.handleReadMoreClick()}
|
||||
className="pr-2"
|
||||
simpleView={true}
|
||||
/>
|
||||
{this.renderActionPanel()}
|
||||
</div>
|
||||
|
@ -196,6 +195,7 @@ class ProjectCard extends React.Component {
|
|||
creatorClickHandler={(event, name) =>
|
||||
this.handleCreatorClick(event, name)
|
||||
}
|
||||
className="my-1 body-small"
|
||||
/>
|
||||
{this.props.onModerationMode && (
|
||||
<Description description={this.props.description} />
|
||||
|
|
|
@ -10,11 +10,15 @@
|
|||
}
|
||||
|
||||
.thumbnail {
|
||||
@include aspect-ratio(1200, 630);
|
||||
@include aspect-ratio(1200, 900);
|
||||
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
|
||||
&.simple-view {
|
||||
@include aspect-ratio(1200, 630);
|
||||
}
|
||||
|
||||
.img-container {
|
||||
background-color: #eee;
|
||||
background-blend-mode: multiply;
|
||||
|
@ -30,19 +34,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.action-panel {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.get-involved,
|
||||
&.moderation-mode .issues-and-tags,
|
||||
&.detail-view .action-panel {
|
||||
border-bottom: 1px solid $black;
|
||||
}
|
||||
|
||||
.share {
|
||||
.btn {
|
||||
width: 20px;
|
||||
|
@ -103,12 +94,6 @@
|
|||
.moderation-panel {
|
||||
background: $gray-20;
|
||||
}
|
||||
|
||||
@media screen and (min-width: $bp-md) {
|
||||
&.detail-view .report-correction {
|
||||
border-top: 1px solid $gray-lighter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.project-card.regular-list-mode {
|
||||
|
@ -133,31 +118,12 @@
|
|||
.project-card.moderation-mode {
|
||||
margin-bottom: 3rem;
|
||||
|
||||
.get-involved {
|
||||
.help-needed {
|
||||
border-top: 1px solid $black;
|
||||
border-bottom: 1px solid $black;
|
||||
|
||||
h2 {
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Detailed view styling
|
||||
.project-card.detail-view {
|
||||
background: inherit;
|
||||
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.heart {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.btn-visit {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.issues-and-tags {
|
||||
color: $dark-blue;
|
||||
}
|
||||
|
|
|
@ -155,9 +155,7 @@ class Bookmarks extends React.Component {
|
|||
<Helmet>
|
||||
<title>Favs</title>
|
||||
</Helmet>
|
||||
<main className="favs container mt-5">
|
||||
{this.renderContent()}
|
||||
</main>
|
||||
<main className="favs container mt-5">{this.renderContent()}</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -115,13 +115,7 @@ class Entry extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="container mt-5">
|
||||
<div className="row justify-content-center">
|
||||
{this.renderEntry()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return this.renderEntry();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
display: block;
|
||||
width: 16px;
|
||||
height: 14px;
|
||||
background: url(../_images/glyphs/external.svg) no-repeat;
|
||||
background: url(../assets/glyphs/external.svg) no-repeat 0 0 / contain;
|
||||
margin-left: 8px;
|
||||
position: relative;
|
||||
bottom: 1px;
|
||||
|
|
|
@ -41,6 +41,13 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.large {
|
||||
&::before {
|
||||
width: 37px;
|
||||
height: 37px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.twitter-glyph {
|
||||
|
@ -84,6 +91,11 @@
|
|||
@include hoverGlyph("signupin-hover");
|
||||
}
|
||||
|
||||
.circle-twitter {
|
||||
@include compositeGlyph("twitter-entry-detailed");
|
||||
@include hoverGlyph("twitter-entry-detailed-hover");
|
||||
}
|
||||
|
||||
.dark-theme {
|
||||
.twitter-glyph {
|
||||
@include compositeGlyph("twitter-dark-theme");
|
||||
|
|
|
@ -224,6 +224,7 @@ fieldset {
|
|||
|
||||
@import '../components/navbar/navbar';
|
||||
@import '../components/project-card/project-card';
|
||||
@import '../components/project-card/project-card-detailed';
|
||||
@import '../components/profile-card/profile-card';
|
||||
@import '../components/footer/footer';
|
||||
@import '../components/issue-selector/issue-selector';
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
.btn-secondary {
|
||||
justify-content: center;
|
||||
|
||||
@mixin active-style() {
|
||||
color: $white;
|
||||
background: $black;
|
||||
|
|