Added some basic shared components, utils, hooks
This commit is contained in:
19
client/src/components/App/App.jsx
Normal file
19
client/src/components/App/App.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
|
||||
import Toast from './Toast';
|
||||
import Routes from './Routes';
|
||||
import NormalizeStyles from './NormalizeStyles';
|
||||
import FontStyles from './FontStyles';
|
||||
import BaseStyles from './BaseStyles';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<NormalizeStyles />
|
||||
<FontStyles />
|
||||
<BaseStyles />
|
||||
<Toast />
|
||||
<Routes />
|
||||
</>
|
||||
);
|
||||
|
||||
export default App;
|
||||
6
client/src/components/App/AppStyles.js
Normal file
6
client/src/components/App/AppStyles.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Main = styled.main`
|
||||
width: 100%;
|
||||
padding-left: 75px;
|
||||
`;
|
||||
108
client/src/components/App/BaseStyles.js
Normal file
108
client/src/components/App/BaseStyles.js
Normal file
@@ -0,0 +1,108 @@
|
||||
import { createGlobalStyle } from 'styled-components';
|
||||
|
||||
import { color, font, mixin } from 'shared/utils/styles';
|
||||
|
||||
export default createGlobalStyle`
|
||||
html, body, #root {
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
color: ${color.textDarkest};
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
line-height: 1.2;
|
||||
${font.size(16)}
|
||||
${font.regular}
|
||||
}
|
||||
|
||||
#root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
${font.regular}
|
||||
}
|
||||
|
||||
*, *:after, *:before, input[type="search"] {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a, a:hover, a:visited, a:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
ul, li, ol, dd, h1, h2, h3, h4, h5, h6, p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6, strong {
|
||||
${font.bold}
|
||||
}
|
||||
|
||||
button {
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Workaround for IE11 focus highlighting for select elements */
|
||||
select::-ms-value {
|
||||
background: none;
|
||||
color: #42413d;
|
||||
}
|
||||
|
||||
[role="button"], button, input, select, textarea {
|
||||
outline: none;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
&:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
[role="button"], button, input, textarea {
|
||||
appearance: none;
|
||||
}
|
||||
select:-moz-focusring {
|
||||
color: transparent;
|
||||
text-shadow: 0 0 0 #000;
|
||||
}
|
||||
select::-ms-expand {
|
||||
display: none;
|
||||
}
|
||||
select option {
|
||||
color: ${color.textDarkest};
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.6;
|
||||
a {
|
||||
${mixin.link()}
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
body, select {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
html {
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
${mixin.placeholderColor(color.textLightBlue)}
|
||||
`;
|
||||
53
client/src/components/App/FontStyles.js
Normal file
53
client/src/components/App/FontStyles.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import { createGlobalStyle } from 'styled-components';
|
||||
|
||||
import BlackWoff2 from 'shared/assets/fonts/CircularStd-Black.woff2';
|
||||
import BoldWoff2 from 'shared/assets/fonts/CircularStd-Bold.woff2';
|
||||
import MediumWoff2 from 'shared/assets/fonts/CircularStd-Medium.woff2';
|
||||
import BookWoff2 from 'shared/assets/fonts/CircularStd-Book.woff2';
|
||||
import BlackWoff from 'shared/assets/fonts/CircularStd-Black.woff';
|
||||
import BoldWoff from 'shared/assets/fonts/CircularStd-Bold.woff';
|
||||
import MediumWoff from 'shared/assets/fonts/CircularStd-Medium.woff';
|
||||
import BookWoff from 'shared/assets/fonts/CircularStd-Book.woff';
|
||||
import IconsSvg from 'shared/assets/icons/jira.svg';
|
||||
import IconsTtf from 'shared/assets/icons/jira.ttf';
|
||||
import IconsWoff from 'shared/assets/icons/jira.woff';
|
||||
|
||||
export default createGlobalStyle`
|
||||
@font-face {
|
||||
font-family: "CircularStdBlack";
|
||||
src: url("${BlackWoff2}") format("woff2"),
|
||||
url("${BlackWoff}") format("woff");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "CircularStdBold";
|
||||
src: url("${BoldWoff2}") format("woff2"),
|
||||
url("${BoldWoff}") format("woff");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "CircularStdMedium";
|
||||
src: url("${MediumWoff2}") format("woff2"),
|
||||
url("${MediumWoff}") format("woff");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "CircularStdBook";
|
||||
src: url("${BookWoff2}") format("woff2"),
|
||||
url("${BookWoff}") format("woff");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "jira";
|
||||
src:
|
||||
url("${IconsTtf}") format("truetype"),
|
||||
url("${IconsWoff}") format("woff"),
|
||||
url("${IconsSvg}#jira") format("svg");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
`;
|
||||
107
client/src/components/App/NavbarLeft/Styles.js
Normal file
107
client/src/components/App/NavbarLeft/Styles.js
Normal file
@@ -0,0 +1,107 @@
|
||||
import styled from 'styled-components';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
import { font, sizes, color, mixin, zIndexValues } from 'shared/utils/styles';
|
||||
import Logo from 'shared/components/Logo';
|
||||
|
||||
export const NavLeft = styled.aside`
|
||||
z-index: ${zIndexValues.navLeft};
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow-x: hidden;
|
||||
height: 100%;
|
||||
width: ${sizes.appNavBarLeftWidth}px;
|
||||
background: ${color.primary};
|
||||
transition: all 0.1s;
|
||||
${mixin.hardwareAccelerate}
|
||||
&:hover {
|
||||
width: 260px;
|
||||
box-shadow: 0 0 50px 0 rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
`;
|
||||
|
||||
export const LogoLink = styled(NavLink)`
|
||||
display: block;
|
||||
position: relative;
|
||||
left: 0;
|
||||
margin: 40px 0 40px;
|
||||
transition: left 0.1s;
|
||||
&:before {
|
||||
display: inline-block;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 50px;
|
||||
width: 20px;
|
||||
background: ${color.primary};
|
||||
}
|
||||
${NavLeft}:hover & {
|
||||
left: 3px;
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledLogo = styled(Logo)`
|
||||
display: inline-block;
|
||||
margin-left: 13px;
|
||||
padding: 10px;
|
||||
${mixin.clickable}
|
||||
`;
|
||||
|
||||
export const IconLink = styled(NavLink)`
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
padding-left: 67px;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
transition: color 0.1s;
|
||||
${mixin.clickable}
|
||||
&:before {
|
||||
content: '';
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 0;
|
||||
height: 50px;
|
||||
width: 5px;
|
||||
background: #fff;
|
||||
border-radius: 6px 0 0 6px;
|
||||
}
|
||||
&.active,
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
&.active:before {
|
||||
display: inline-block;
|
||||
}
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
i {
|
||||
position: absolute;
|
||||
left: 27px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const LinkText = styled.div`
|
||||
position: relative;
|
||||
right: 12px;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
text-transform: uppercase;
|
||||
transition: all 0.1s;
|
||||
transition-property: right, visibility, opacity;
|
||||
${font.bold}
|
||||
${font.size(12)}
|
||||
${NavLeft}:hover & {
|
||||
right: 0;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
26
client/src/components/App/NavbarLeft/index.jsx
Normal file
26
client/src/components/App/NavbarLeft/index.jsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Icon } from 'shared/components';
|
||||
import { NavLeft, LogoLink, StyledLogo, IconLink, LinkText } from './Styles';
|
||||
|
||||
const NavbarLeft = () => (
|
||||
<NavLeft>
|
||||
<LogoLink to="/">
|
||||
<StyledLogo color="#fff" />
|
||||
</LogoLink>
|
||||
<IconLink to="/projects">
|
||||
<Icon type="archive" size={16} />
|
||||
<LinkText>Projects</LinkText>
|
||||
</IconLink>
|
||||
<IconLink to="/subcontractors">
|
||||
<Icon type="briefcase" size={16} />
|
||||
<LinkText>Subcontractors</LinkText>
|
||||
</IconLink>
|
||||
<IconLink to="/bids">
|
||||
<Icon type="file-text" size={20} left={-2} />
|
||||
<LinkText>Bids</LinkText>
|
||||
</IconLink>
|
||||
</NavLeft>
|
||||
);
|
||||
|
||||
export default NavbarLeft;
|
||||
152
client/src/components/App/NormalizeStyles.js
Normal file
152
client/src/components/App/NormalizeStyles.js
Normal file
@@ -0,0 +1,152 @@
|
||||
import { createGlobalStyle } from 'styled-components';
|
||||
|
||||
/** DO NOT ALTER THIS FILE. It is a copy of https://necolas.github.io/normalize.css/ */
|
||||
|
||||
export default createGlobalStyle`
|
||||
html {
|
||||
line-height: 1.15;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
hr {
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
pre {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
abbr[title] {
|
||||
border-bottom: none;
|
||||
text-decoration: underline;
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit;
|
||||
font-size: 100%;
|
||||
line-height: 1.15;
|
||||
margin: 0;
|
||||
}
|
||||
button,
|
||||
input {
|
||||
overflow: visible;
|
||||
}
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
legend {
|
||||
box-sizing: border-box;
|
||||
color: inherit;
|
||||
display: table;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
white-space: normal;
|
||||
}
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button;
|
||||
font: inherit;
|
||||
}
|
||||
details {
|
||||
display: block;
|
||||
}
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
21
client/src/components/App/Routes.jsx
Normal file
21
client/src/components/App/Routes.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import history from 'browserHistory';
|
||||
import { Router, Switch, Route } from 'react-router-dom';
|
||||
|
||||
import PageNotFound from 'components/PageNotFound';
|
||||
import NavbarLeft from './NavbarLeft';
|
||||
|
||||
import { Main } from './AppStyles';
|
||||
|
||||
const Routes = () => (
|
||||
<Router history={history}>
|
||||
<Main>
|
||||
<NavbarLeft />
|
||||
<Switch>
|
||||
<Route component={PageNotFound} />
|
||||
</Switch>
|
||||
</Main>
|
||||
</Router>
|
||||
);
|
||||
|
||||
export default Routes;
|
||||
58
client/src/components/App/Toast/Styles.js
Normal file
58
client/src/components/App/Toast/Styles.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { color, font, mixin, zIndexValues } from 'shared/utils/styles';
|
||||
|
||||
export const Container = styled.div`
|
||||
z-index: ${zIndexValues.modal + 1};
|
||||
position: fixed;
|
||||
right: 30px;
|
||||
top: 50px;
|
||||
`;
|
||||
|
||||
export const StyledToast = styled.div`
|
||||
position: relative;
|
||||
margin-bottom: 5px;
|
||||
width: 300px;
|
||||
padding: 15px 20px;
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
background: ${props => color[props.type]};
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
${mixin.clearfix}
|
||||
${mixin.hardwareAccelerate}
|
||||
|
||||
&.jira-toast-enter,
|
||||
&.jira-toast-exit.jira-toast-exit-active {
|
||||
opacity: 0;
|
||||
right: -10px;
|
||||
}
|
||||
|
||||
&.jira-toast-exit,
|
||||
&.jira-toast-enter.jira-toast-enter-active {
|
||||
opacity: 1;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
top: 13px;
|
||||
right: 14px;
|
||||
font-size: 22px;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Title = styled.div`
|
||||
padding-right: 22px;
|
||||
${font.size(16)}
|
||||
${font.medium}
|
||||
`;
|
||||
|
||||
export const Message = styled.div`
|
||||
padding: 8px 10px 0 0;
|
||||
white-space: pre-wrap;
|
||||
${font.size(14)}
|
||||
${font.medium}
|
||||
`;
|
||||
62
client/src/components/App/Toast/index.jsx
Normal file
62
client/src/components/App/Toast/index.jsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import React, { Component } from 'react';
|
||||
import { CSSTransition, TransitionGroup } from 'react-transition-group';
|
||||
import pubsub from 'sweet-pubsub';
|
||||
import { uniqueId } from 'lodash';
|
||||
|
||||
import { Icon } from 'shared/components';
|
||||
import { Container, StyledToast, Title, Message } from './Styles';
|
||||
|
||||
class Toast extends Component {
|
||||
state = { toasts: [] };
|
||||
|
||||
componentDidMount() {
|
||||
pubsub.on('toast', this.addToast);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
pubsub.off('toast', this.addToast);
|
||||
}
|
||||
|
||||
addToast = ({ type = 'success', title, message, duration = 5 }) => {
|
||||
const id = uniqueId('toast-');
|
||||
|
||||
this.setState(state => ({
|
||||
toasts: [...state.toasts, { id, type, title, message }],
|
||||
}));
|
||||
|
||||
if (duration) {
|
||||
setTimeout(() => this.removeToast(id), duration * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
removeToast = id => {
|
||||
this.setState(state => ({
|
||||
toasts: state.toasts.filter(toast => toast.id !== id),
|
||||
}));
|
||||
};
|
||||
|
||||
render() {
|
||||
const { toasts } = this.state;
|
||||
return (
|
||||
<Container>
|
||||
<TransitionGroup>
|
||||
{toasts.map(toast => (
|
||||
<CSSTransition key={toast.id} classNames="jira-toast" timeout={200}>
|
||||
<StyledToast
|
||||
key={toast.id}
|
||||
type={toast.type}
|
||||
onClick={() => this.removeToast(toast.id)}
|
||||
>
|
||||
<Icon type="close" />
|
||||
{toast.title && <Title>{toast.title}</Title>}
|
||||
{toast.message && <Message>{toast.message}</Message>}
|
||||
</StyledToast>
|
||||
</CSSTransition>
|
||||
))}
|
||||
</TransitionGroup>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Toast;
|
||||
Reference in New Issue
Block a user