Skip to content
This repository has been archived by the owner on Apr 9, 2023. It is now read-only.

Commit

Permalink
feature: add github user components (#28)
Browse files Browse the repository at this point in the history
* feature: add avatar atom component
* feature: add user molecule component
* feature: add github provider and user organism
* refactor: remove default app page
* feature: add primary app page
* chore: update package file with used dependencies
* chore: disabled service workers for first version
* chore: add basic styling rules to template
* style: remove white line in import statements
* pipeline: clean up flow and temporary disable tests
  • Loading branch information
byCedric authored Oct 13, 2018
1 parent e169bd0 commit 94fd537
Show file tree
Hide file tree
Showing 20 changed files with 245 additions and 83 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ script:
- npx commitlint-travis
- npx stylelint ./src/**/*.js
- npx eslint ./src
- npx flow
- npm test -- --coverage
after_success:
- npx codecov
Expand Down
21 changes: 13 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,26 @@
"type": "git",
"url": "https://github.com/bycedric/github-website.git"
},
"dependencies": {
"react": "^16.5.2",
"react-dom": "^16.5.2",
"react-scripts": "2.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"test": "echo 'No tests specified (yet)'; exit 0",
"eject": "react-scripts eject"
},
"dependencies": {
"prop-types": "^15.6.2",
"react": "^16.5.2",
"react-async": "^3.8.1",
"react-dom": "^16.5.2",
"styled-components": "^3.4.10"
},
"devDependencies": {
"@commitlint/travis-cli": "^7.1.2",
"@peakfijn/config-commitlint": "^0.8.0",
"@semantic-release/changelog": "^3.0.0",
"codecov": "^3.1.0",
"conventional-changelog-peakfijn": "^0.8.0",
"flow-bin": "^0.81.0",
"react-scripts": "2.0.4",
"release-rules-peakfijn": "^0.8.0",
"semantic-release": "^15.9.16",
"semantic-release-git-branches": "^1.2.1",
Expand All @@ -48,7 +50,10 @@
"eslintConfig": {
"extends": "react-app",
"rules": {
"indent": ["error", "tab"]
"indent": [
"error",
"tab"
]
}
},
"stylelint": {
Expand Down
8 changes: 6 additions & 2 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
-->
<title>React App</title>
<style type="text/css">
html, body, #root {
height: 100%;
}

body {
margin: 0;
padding: 0;
Expand All @@ -29,8 +33,8 @@
-moz-osx-font-smoothing: grayscale;
}

code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
* {
box-sizing: border-box;
}
</style>
</head>
Expand Down
28 changes: 28 additions & 0 deletions src/atoms/avatar/avatar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import PropTypes from 'prop-types';
import { AvatarContainer, AvatarImage } from './elements';

export default function AvatarAtom(props) {
return (
<AvatarContainer>
<AvatarImage
src={props.url}
alt={props.name}
title={props.title}
/>
</AvatarContainer>
);
}

AvatarAtom.propTypes = {
/** The absolute URL to the image. */
url: PropTypes.string.isRequired,
/** The name of the person who is displayed, to use as the alternative text for the image. */
name: PropTypes.string.isRequired,
/** The title to display when hovering the avatar. */
title: PropTypes.string,
};

AvatarAtom.propTypes = {
title: undefined,
};
25 changes: 25 additions & 0 deletions src/atoms/avatar/elements.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import styled from 'styled-components';

/**
* The avatar container "masks" the underlying image and makes it circular.
* It creates an element with rounded corners with overflow hidden.
*/
export const AvatarContainer = styled.div`
display: block;
border-radius: 50%;
background: currentColor;
width: 3.75em;
height: 3.75em;
overflow: hidden;
user-select: none;
`;

/**
* The avatar image contains the actual image to display, set to fully "cover" the element.
* To fully cover the avatar the image will be scaled and cropped maintaining aspect ratio.
*/
export const AvatarImage = styled.img`
width: 100%;
height: 100%;
object-fit: cover;
`;
1 change: 1 addition & 0 deletions src/atoms/avatar/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './avatar';
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
import 'src/providers/react';
import 'src/providers/service-worker';
// import 'src/providers/service-worker';
54 changes: 54 additions & 0 deletions src/molecules/user/elements.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import styled from 'styled-components';

/**
* The user container creates a vertical-directed flexbox.
* It also centers the content to connect the otherwise unrelated content visually.
*/
export const UserContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
color: #6a737d;
`;

/**
* The user avatar aligns the user's avatar with the name, using flexbox.
* This technique is also applied for the description to center the user's name correctly.
*/
export const UserAvatar = styled.div`
display: flex;
flex: 1;
align-items: flex-end;
padding: 0.5rem;
font-size: 1.5em;
`;

/**
* The user's name is also the title of the app and is therefore vertically centered.
* It's positioned with flexbox to achieve this without knowing the sizes of both the avatar and description.
*/
export const UserName = styled.h1`
flex: 0;
margin: 0;
padding: 0.5rem;
line-height: 1.5;
color: #24292e;
font-size: 2em;
`;

/**
* The user description aligns the user's description with the name, using flexbox.
* Because of this flexbox positioning, the size of the text has no impact on the alignment.
* This technique is also applied for the avatar to center the name correctly.
*/
export const UserDescription = styled.p`
display: flex;
flex: 1;
align-items: flex-start;
margin: 0;
padding: 0.5rem;
width: 100%;
max-width: 24em;
text-align: center;
line-height: 1.5;
`;
1 change: 1 addition & 0 deletions src/molecules/user/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './user';
46 changes: 46 additions & 0 deletions src/molecules/user/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';
import PropTypes from 'prop-types';
import Avatar from 'src/atoms/avatar';
import {
UserContainer,
UserAvatar,
UserName,
UserDescription,
} from './elements';

export default function UserMolecule(props) {
const identifier = `${props.name} (${props.username})`;

return (
<UserContainer>
<UserAvatar>
<Avatar
url={props.avatarUrl}
name={identifier}
title={identifier}
/>
</UserAvatar>
<UserName title={identifier}>
{props.name}
</UserName>
<UserDescription>
{props.description}
</UserDescription>
</UserContainer>
);
}

UserMolecule.propTypes = {
/** The full name of the person who is displayed. */
name: PropTypes.string.isRequired,
/** The username of the person who is displayed. */
username: PropTypes.string.isRequired,
/** The absolute URL of the person's avatar. */
avatarUrl: PropTypes.string.isRequired,
/** An optional description of this person. */
description: PropTypes.string,
};

UserMolecule.defaultProps = {
description: '',
};
20 changes: 20 additions & 0 deletions src/organisms/github-user/github-user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import { AsyncUser } from 'src/providers/github';
import User from 'src/molecules/user';

export default function GithubUserOrganism() {
return (
<AsyncUser>
<AsyncUser.Resolved>
{data => (
<User
name={data.name}
username={data.login}
avatarUrl={data.avatar_url}
description={data.bio}
/>
)}
</AsyncUser.Resolved>
</AsyncUser>
);
}
1 change: 1 addition & 0 deletions src/organisms/github-user/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './github-user';
32 changes: 0 additions & 32 deletions src/pages/app/App.css

This file was deleted.

28 changes: 0 additions & 28 deletions src/pages/app/App.js

This file was deleted.

9 changes: 0 additions & 9 deletions src/pages/app/App.test.js

This file was deleted.

11 changes: 11 additions & 0 deletions src/pages/app/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import GithubUser from 'src/organisms/github-user';
import { AppContainer } from './elements';

export default function AppPage() {
return (
<AppContainer>
<GithubUser />
</AppContainer>
);
}
11 changes: 11 additions & 0 deletions src/pages/app/elements.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import styled from 'styled-components';

/**
* The app container wraps the content and centers it both horizontally and vertically.
*/
export const AppContainer = styled.div`
display: flex;
justify-content: center;
width: 100%;
height: 100%;
`;
2 changes: 1 addition & 1 deletion src/pages/app/index.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { default } from './App';
export { default } from './app';
26 changes: 26 additions & 0 deletions src/providers/github.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { createInstance } from 'react-async';

/**
* The GitHub username to fetch information from.
*/
export const username = process.env.GITHUB_USERNAME || 'byCedric';

/**
* Fetch the user information from the GitHub API.
*
* @see https://developer.github.com/v3/users/#get-a-single-user
* @return {Promise}
*/
export const fetchUser = () => (
fetch(`https://api.github.com/users/${username}`)
.then(response => response.json())
)

/**
* Export a predefined React Async component instance.
* This creates a custom context for improved nesting and predefines the promise.
*
* @see https://github.com/ghengeveld/react-async
* @return {React.Component}
*/
export const AsyncUser = createInstance({ promiseFn: fetchUser });
1 change: 0 additions & 1 deletion src/providers/react.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import { render } from 'react-dom';

import App from 'src/pages/app';

render(<App />, document.getElementById('root'));

0 comments on commit 94fd537

Please sign in to comment.