Skip to content

Commit 16353bb

Browse files
SaraVieiraCompuIves
authored andcommitted
New Cards (codesandbox#1865)
* new cards * fix small homepage issues * fix a11y and sopacing * fix black line * fix gatsby * do not show overlay on top of border * pr fixes * add million support * Update packages/common/src/components/SandboxCard/index.tsx * Make million threshold higher * Break words if they are too big
1 parent 57edaee commit 16353bb

File tree

5 files changed

+359
-9
lines changed

5 files changed

+359
-9
lines changed

packages/app/src/app/pages/Search/Results/index.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,25 @@ import React from 'react';
22

33
import { Hits, Pagination } from 'react-instantsearch/dom';
44
import Centered from '@codesandbox/common/lib/components/flex/Centered';
5-
import WideSandbox from '@codesandbox/common/lib/components/WideSandbox';
5+
import SandboxCard from '@codesandbox/common/lib/components/SandboxCard';
66
import { sandboxUrl } from '@codesandbox/common/lib/utils/url-generator';
77
import Margin from '@codesandbox/common/lib/components/spacing/Margin';
88

99
import ResultInfo from '../ResultInfo';
1010
import { Container } from './elements';
1111

12-
function Results() {
12+
const Results = () => {
13+
const selectSandbox = hit =>
14+
window.open(sandboxUrl({ id: hit.objectID, git: hit.git }));
15+
1316
return (
1417
<Container>
1518
<ResultInfo />
1619
<Margin bottom={2}>
1720
<Hits
1821
hitComponent={({ hit }) => (
19-
<WideSandbox
20-
selectSandbox={() =>
21-
window.open(sandboxUrl({ id: hit.objectID, git: hit.git }))
22-
}
22+
<SandboxCard
23+
selectSandbox={() => selectSandbox(hit)}
2324
noHeight
2425
sandbox={{
2526
...hit,
@@ -35,6 +36,6 @@ function Results() {
3536
</Centered>
3637
</Container>
3738
);
38-
}
39+
};
3940

4041
export default Results;
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import styled, { css } from 'styled-components';
2+
3+
import { UserWithAvatar } from '../UserWithAvatar';
4+
5+
export const BG_COLOR = '#1C2022';
6+
export const BG_HOVER = '#212629';
7+
8+
export const Overlay = styled.div`
9+
position: absolute;
10+
background: rgba(28, 32, 34, 0.9);
11+
border-radius: 4px 4px 0px 0px;
12+
top: 0;
13+
left: 0;
14+
width: 100%;
15+
height: calc(100% - 3px);
16+
padding: 1rem;
17+
box-sizing: border-box;
18+
opacity: 0;
19+
transition: opacity 200ms ease;
20+
`;
21+
22+
export const Container = styled.div<{ small?: boolean; noMargin?: boolean }>`
23+
transition: 0.3s ease all;
24+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
25+
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
26+
position: relative;
27+
flex: 1;
28+
min-width: 300px;
29+
30+
flex-grow: 1;
31+
width: 100%;
32+
border-radius: 4px;
33+
overflow: hidden;
34+
cursor: pointer;
35+
36+
margin-right: 0.5rem;
37+
margin-left: 0.5rem;
38+
39+
background-color: ${BG_COLOR};
40+
box-shadow: 0 0 0 rgba(0, 0, 0, 0.3);
41+
42+
${props =>
43+
props.small &&
44+
css`
45+
min-width: auto;
46+
`};
47+
48+
${props =>
49+
props.noMargin &&
50+
css`
51+
margin: 0;
52+
`};
53+
54+
&:hover {
55+
${Overlay} {
56+
opacity: 1;
57+
}
58+
}
59+
60+
&:hover {
61+
background-color: ${BG_HOVER};
62+
transform: translateY(-5px);
63+
box-shadow: 0 8px 4px rgba(0, 0, 0, 0.3);
64+
}
65+
66+
&:last-child {
67+
flex-grow: 0;
68+
min-width: calc(33% - 1rem);
69+
}
70+
`;
71+
72+
export const SandboxTitle = styled.h2`
73+
color: ${props => props.color};
74+
font-family: 'Poppins', sans-serif;
75+
font-size: 0.75rem;
76+
font-weight: 600;
77+
margin-bottom: 6px;
78+
margin-top: 0;
79+
80+
overflow: hidden;
81+
text-overflow: ellipsis;
82+
display: -webkit-box;
83+
-webkit-box-orient: vertical;
84+
-webkit-line-clamp: 1;
85+
max-height: 20px;
86+
87+
word-break: break-all;
88+
`;
89+
90+
export const SandboxDescription = styled.p`
91+
font-size: 0.8rem;
92+
color: ${props => props.theme.lightText};
93+
font-weight: 500;
94+
line-height: 1.3;
95+
margin: 0;
96+
margin-bottom: 16px;
97+
font-size: 12px;
98+
`;
99+
100+
export const SandboxImage = styled.img`
101+
display: block;
102+
margin-bottom: 0;
103+
z-index: 0;
104+
border-bottom: 3px solid ${props => props.color};
105+
height: auto;
106+
width: 100%;
107+
background-color: ${BG_HOVER};
108+
border-image-width: 0;
109+
`;
110+
111+
export const SandboxInfo = styled.div<{ noHeight?: boolean }>`
112+
left: -1px;
113+
right: -1px;
114+
padding: 0.75rem;
115+
padding-bottom: 4px;
116+
z-index: 1;
117+
display: flex;
118+
justify-content: space-between;
119+
`;
120+
121+
export const TemplateIcon = styled.div`
122+
display: flex;
123+
`;
124+
125+
export const Author = styled(UserWithAvatar)`
126+
font-size: 0.75rem;
127+
font-weight: 600;
128+
text-decoration: none;
129+
color: ${props => props.theme.new.description};
130+
`;
131+
132+
export const Stats = styled.ul`
133+
list-style: none;
134+
padding: 0;
135+
display: flex;
136+
font-family: 'Open Sans', 'Helvetica Neue', sans-serif;
137+
font-size: 12px;
138+
line-height: 16px;
139+
display: flex;
140+
align-items: center;
141+
142+
color: ${props => props.theme.placeholder};
143+
144+
li:not(:last-child) {
145+
margin-right: 8px;
146+
}
147+
148+
li {
149+
display: flex;
150+
align-items: center;
151+
152+
svg {
153+
margin-right: 6px;
154+
width: 16px;
155+
color: ${props => props.theme.placeholder.darken(0.3)};
156+
}
157+
}
158+
`;
159+
160+
export const Avatar = styled.img`
161+
width: 1rem;
162+
height: 1rem;
163+
border-radius: 4px;
164+
`;
165+
166+
export const SandboxStats = styled.div`
167+
display: flex;
168+
padding: 0.75rem;
169+
justify-content: space-between;
170+
`;
171+
172+
export const Image = styled.div`
173+
position: relative;
174+
font-size: 10px;
175+
`;
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import * as React from 'react';
2+
import ForkIcon from 'react-icons/lib/go/repo-forked';
3+
import EyeIcon from 'react-icons/lib/go/eye';
4+
import LikeIcon from 'react-icons/lib/go/heart';
5+
6+
import getIcon from '../../templates/icons';
7+
import getTemplate, { TemplateType } from '../../templates';
8+
import { profileUrl } from '../../utils/url-generator';
9+
import { ENTER } from '../../utils/keycodes';
10+
11+
import {
12+
Container,
13+
SandboxTitle,
14+
SandboxDescription,
15+
Image,
16+
Overlay,
17+
Avatar,
18+
Stats,
19+
SandboxStats,
20+
SandboxImage,
21+
SandboxInfo,
22+
TemplateIcon,
23+
Author,
24+
} from './elements';
25+
import Tags from '../Tags';
26+
27+
const getScreenshot = (id: string) =>
28+
`https://codesandbox.io/api/v1/sandboxes/${id}/screenshot.png`;
29+
30+
type Sandbox = {
31+
title: string;
32+
description: string;
33+
tags: string[];
34+
id: string;
35+
screenshot_url: string;
36+
template: TemplateType;
37+
view_count: number;
38+
fork_count: number;
39+
like_count: number;
40+
author: {
41+
username: string;
42+
avatar_url: string;
43+
};
44+
};
45+
46+
export type Props = {
47+
sandbox: Sandbox;
48+
small?: boolean;
49+
noHeight?: boolean;
50+
defaultHeight?: number;
51+
noMargin?: boolean;
52+
selectSandbox: (params: Sandbox) => void;
53+
};
54+
55+
const kFormatter = (num: number): number | string => {
56+
if (num > 999999) {
57+
return (num / 1000000).toFixed(1) + 'M';
58+
}
59+
60+
if (num > 999) {
61+
return (num / 1000).toFixed(1) + 'K';
62+
}
63+
64+
return num;
65+
};
66+
67+
export default class SandboxCard extends React.PureComponent<Props> {
68+
state = {
69+
imageLoaded: false,
70+
};
71+
72+
toggleOpen = () => {
73+
this.props.selectSandbox({ ...this.props.sandbox });
74+
};
75+
76+
handleKeyUp = e => {
77+
if (e.keyCode === ENTER) {
78+
this.toggleOpen();
79+
}
80+
};
81+
82+
render() {
83+
const {
84+
sandbox,
85+
small,
86+
noMargin,
87+
noHeight,
88+
defaultHeight = 152,
89+
} = this.props;
90+
if (!sandbox) {
91+
return (
92+
<Container style={{}}>
93+
<SandboxImage as="div" style={{ border: 0, height: 150 }} />
94+
<SandboxInfo />
95+
</Container>
96+
);
97+
}
98+
const template = getTemplate(sandbox.template);
99+
const Icon = getIcon(sandbox.template);
100+
return (
101+
<Container
102+
noMargin={noMargin}
103+
small={small}
104+
style={{}}
105+
onClick={this.toggleOpen}
106+
role="button"
107+
tabIndex={0}
108+
onKeyUp={this.handleKeyUp}
109+
>
110+
<Image>
111+
<SandboxImage
112+
alt={sandbox.title}
113+
src={sandbox.screenshot_url || getScreenshot(sandbox.id)}
114+
color={template.color()}
115+
style={{ height: defaultHeight }}
116+
ref={img => {
117+
if (img && img.complete) {
118+
this.setState({ imageLoaded: true });
119+
}
120+
}}
121+
onLoad={() => {
122+
this.setState({ imageLoaded: true });
123+
}}
124+
/>
125+
126+
<Overlay>
127+
<SandboxDescription>{sandbox.description}</SandboxDescription>
128+
<Tags tags={sandbox.tags} />
129+
</Overlay>
130+
</Image>
131+
<SandboxInfo noHeight={noHeight}>
132+
<SandboxTitle color={template.color()}>{sandbox.title}</SandboxTitle>
133+
<TemplateIcon>
134+
<Icon width={16} height={16} />
135+
</TemplateIcon>
136+
</SandboxInfo>
137+
138+
<SandboxStats>
139+
<Stats>
140+
<li>
141+
<EyeIcon />
142+
{kFormatter(sandbox.view_count)}
143+
</li>
144+
<li>
145+
<ForkIcon />
146+
{kFormatter(sandbox.fork_count)}
147+
</li>
148+
<li>
149+
<LikeIcon />
150+
{kFormatter(sandbox.like_count)}
151+
</li>
152+
</Stats>
153+
{sandbox.author && (
154+
<a href={profileUrl(sandbox.author.username)}>
155+
<Avatar src={sandbox.author.avatar_url} />
156+
</a>
157+
)}
158+
</SandboxStats>
159+
</Container>
160+
);
161+
}
162+
}

packages/homepage/src/pages/blog.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,15 @@ const Blog = ({ data: { allFeedMediumBlog, allMarkdownRemark } }) => {
6060
<Info post={post} />
6161
<Posts>
6262
{post.src && (
63-
<Thumbnail src={post.src} width="340" alt={post.title} />
63+
<Link
64+
css={`
65+
text-decoration: none;
66+
display: contents;
67+
`}
68+
to={`post/${post.slug}`}
69+
>
70+
<Thumbnail src={post.src} width="340" alt={post.title} />
71+
</Link>
6472
)}
6573
<div>
6674
<Link

0 commit comments

Comments
 (0)