Bug 1519766 - basic List component for DiscoveryStream (WIP), r=thecount (#4654)
This commit is contained in:
Родитель
31ef3d1eca
Коммит
6967f2e7a0
|
@ -47,7 +47,9 @@ export class _DiscoveryStreamBase extends React.PureComponent {
|
|||
case "HorizontalRule":
|
||||
return (<HorizontalRule />);
|
||||
case "List":
|
||||
return (<List feed={component.feed} />);
|
||||
return (<List
|
||||
feed={component.feed}
|
||||
header={component.header} />);
|
||||
default:
|
||||
return (<div>{component.type}</div>);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
|
||||
.outer-wrapper a {
|
||||
color: $grey-90;
|
||||
}
|
||||
|
||||
.discovery-stream.ds-layout {
|
||||
$columns: 12;
|
||||
display: grid;
|
||||
|
|
|
@ -1,15 +1,60 @@
|
|||
import {connect} from "react-redux";
|
||||
import React from "react";
|
||||
|
||||
export class _List extends React.PureComponent {
|
||||
render() {
|
||||
// const feed = this.props.DiscoveryStream.feeds[this.props.feed.url];
|
||||
return (
|
||||
<div className="ds-list">
|
||||
List
|
||||
</div>
|
||||
);
|
||||
}
|
||||
/**
|
||||
* @note exported for testing only
|
||||
*/
|
||||
export function ListItem(props) {
|
||||
// TODO performance: get feeds to send appropriately sized images rather
|
||||
// than waiting longer and scaling down on client?
|
||||
return (
|
||||
<li className="ds-list-item">
|
||||
<a className="ds-list-item-link" href={props.url}>
|
||||
<div className="ds-list-item-text">
|
||||
<div className="ds-list-item-title">
|
||||
<b>
|
||||
{props.title}
|
||||
</b>
|
||||
</div>
|
||||
<div className="ds-list-item-info">
|
||||
{`${props.domain} · TODO:Topic`}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<img className="ds-list-image" src={props.image_src} />
|
||||
</a>
|
||||
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @note exported for testing only
|
||||
*/
|
||||
export function _List(props) {
|
||||
const feed = props.DiscoveryStream.feeds[props.feed.url];
|
||||
|
||||
if (!feed || !feed.data || !feed.data.recommendations) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const recs = feed.data.recommendations;
|
||||
|
||||
let recMarkup = recs.slice(0, props.items).map((rec, index) => (
|
||||
<ListItem {...rec} key={`ds-list-item-$index`} />)
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 className="ds-list-title">{props.header.title}</h3>
|
||||
<hr className="ds-list-border" />
|
||||
<ul className="ds-list">{recMarkup}</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_List.defaultProps = {
|
||||
items: 6, // Number of stories to display. TODO: get from endpoint
|
||||
};
|
||||
|
||||
export const List = connect(state => ({DiscoveryStream: state.DiscoveryStream}))(_List);
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
.ds-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-row-gap: 10px;
|
||||
grid-column-gap: 24px;
|
||||
|
||||
// reset some stuff from <ul>. Should maybe be hoisted when we have better
|
||||
// regression detection?
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
.ds-list-item {
|
||||
// XXX see if we really want absolute units, maybe hoist somewhere central?
|
||||
line-height: 20px;
|
||||
font-size: 13px;
|
||||
|
||||
// reset some stuff from <li>. Should maybe be hoisted when we have better
|
||||
// regression detection?
|
||||
display: block;
|
||||
text-align: start;
|
||||
|
||||
.ds-list-item-link {
|
||||
mix-blend-mode: normal;
|
||||
|
||||
padding-bottom: 16px;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.ds-list-item-info {
|
||||
color: $grey-50;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ds-list-item-title {
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.ds-list-item-text {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ds-list-image {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
object-fit: cover;
|
||||
|
||||
border: 0.5px solid $black-12;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
|
@ -45,6 +45,7 @@ $grey-90-90: rgba($grey-90, 0.9);
|
|||
$black: #000;
|
||||
$black-5: rgba($black, 0.05);
|
||||
$black-10: rgba($black, 0.1);
|
||||
$black-12: rgba($black, 0.12);
|
||||
$black-15: rgba($black, 0.15);
|
||||
$black-20: rgba($black, 0.2);
|
||||
$black-25: rgba($black, 0.25);
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
import {_List as List, ListItem} from "content-src/components/DiscoveryStreamComponents/List/List";
|
||||
import {GlobalOverrider} from "test/unit/utils";
|
||||
import React from "react";
|
||||
import {shallow} from "enzyme";
|
||||
|
||||
describe("<List> presentation component", () => {
|
||||
const ValidRecommendations = [{a: 1}, {a: 2}];
|
||||
const ValidListProps = {
|
||||
DiscoveryStream: {
|
||||
feeds: {
|
||||
fakeFeedUrl: {
|
||||
data: {
|
||||
recommendations: ValidRecommendations,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
feed: {
|
||||
url: "fakeFeedUrl",
|
||||
},
|
||||
header: {
|
||||
title: "fakeFeedTitle",
|
||||
},
|
||||
};
|
||||
|
||||
it("should return null if feed.data is falsy", () => {
|
||||
const ListProps = {
|
||||
DiscoveryStream: {feeds: {a: "stuff"}},
|
||||
feed: {url: "a"},
|
||||
};
|
||||
|
||||
const wrapper = shallow(<List {...ListProps} />);
|
||||
|
||||
assert.isNull(wrapper.getElement());
|
||||
});
|
||||
|
||||
it("should return something containing a <ul> if props are valid", () => {
|
||||
const wrapper = shallow(<List {...ValidListProps} />);
|
||||
|
||||
const list = wrapper.find("ul");
|
||||
assert.ok(wrapper.exists());
|
||||
assert.lengthOf(list, 1);
|
||||
});
|
||||
|
||||
it("should return the right number of ListItems if props are valid", () => {
|
||||
const wrapper = shallow(<List {...ValidListProps} />);
|
||||
|
||||
const listItem = wrapper.find(ListItem);
|
||||
assert.lengthOf(listItem, ValidRecommendations.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe("<ListItem> presentation component", () => {
|
||||
const ValidListItemProps = {
|
||||
url: "FAKE_URL",
|
||||
title: "FAKE_TITLE",
|
||||
domain: "example.com",
|
||||
image_src: "FAKE_IMAGE_SRC",
|
||||
};
|
||||
|
||||
let globals;
|
||||
|
||||
beforeEach(() => {
|
||||
globals = new GlobalOverrider();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
globals.sandbox.restore();
|
||||
});
|
||||
|
||||
it("should contain 'a.ds-list-item-link' with the props.url set", () => {
|
||||
const wrapper = shallow(<ListItem {...ValidListItemProps} />);
|
||||
|
||||
const anchors = wrapper.find(
|
||||
`a.ds-list-item-link[href="${ValidListItemProps.url}"]`);
|
||||
assert.lengthOf(anchors, 1);
|
||||
});
|
||||
|
||||
it("should include an img with a src of props.image_src", () => {
|
||||
const wrapper = shallow(<ListItem {...ValidListItemProps} />);
|
||||
|
||||
const anchors = wrapper.find(`img[src="${ValidListItemProps.image_src}"]`);
|
||||
assert.lengthOf(anchors, 1);
|
||||
});
|
||||
});
|
Загрузка…
Ссылка в новой задаче