diff --git a/.gitignore b/.gitignore index e37b0950e4..7f8b458174 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ node_modules # generated artifacts assets/bundle.js +assets/bundle.css server/bindata.go *.cover *.test diff --git a/frontend/components/LoginRoutes/styles.js b/frontend/components/LoginRoutes/styles.js index 2b6869bc1d..1799599a1c 100644 --- a/frontend/components/LoginRoutes/styles.js +++ b/frontend/components/LoginRoutes/styles.js @@ -3,7 +3,6 @@ export default { alignItems: 'center', display: 'flex', flexDirection: 'column', - paddingTop: '80px', + marginTop: '13vh', }, }; - diff --git a/frontend/components/SidePanel/SidePanel.jsx b/frontend/components/SidePanel/SidePanel.jsx index 67fe0f12cc..cb70b99604 100644 --- a/frontend/components/SidePanel/SidePanel.jsx +++ b/frontend/components/SidePanel/SidePanel.jsx @@ -4,6 +4,7 @@ import { isEqual, last } from 'lodash'; import componentStyles from './styles'; import kolideLogo from '../../../assets/images/kolide-logo.svg'; import navItems from './navItems'; +import './styles.scss'; class SidePanel extends Component { static propTypes = { @@ -41,6 +42,10 @@ class SidePanel extends Component { }; } + setSubNavClass = (showSubItems) => { + return showSubItems ? 'sub-nav sub-nav--expanded' : 'sub-nav'; + } + toggleShowSubItems = (showSubItems) => { return (evt) => { evt.preventDefault(); @@ -64,6 +69,7 @@ class SidePanel extends Component { orgNameStyles, usernameStyles, userStatusStyles, + orgChevronStyles, } = componentStyles; return ( @@ -76,6 +82,7 @@ class SidePanel extends Component {

Kolide, Inc.

{username}

+ ); } @@ -97,6 +104,7 @@ class SidePanel extends Component {
{active &&
}
  • @@ -146,6 +154,7 @@ class SidePanel extends Component { > {active &&
    }
  • @@ -157,13 +166,11 @@ class SidePanel extends Component { renderSubItems = (subItems) => { const { subItemListStyles, subItemsStyles } = componentStyles; - const { renderCollapseSubItems, renderSubItem } = this; + const { renderCollapseSubItems, renderSubItem, setSubNavClass } = this; const { showSubItems } = this.state; - if (!subItems.length) return false; - return ( -
    +
      {subItems.map(subItem => { return renderSubItem(subItem); @@ -181,8 +188,8 @@ class SidePanel extends Component { const iconName = showSubItems ? 'kolidecon-chevron-bold-left' : 'kolidecon-chevron-bold-right'; return ( -
      - +
      +
      ); } diff --git a/frontend/components/SidePanel/styles.js b/frontend/components/SidePanel/styles.js index d1477d4e19..78e2b120dc 100644 --- a/frontend/components/SidePanel/styles.js +++ b/frontend/components/SidePanel/styles.js @@ -5,21 +5,43 @@ const { border, color, font, padding } = Styles; const componentStyles = { companyLogoStyles: { position: 'absolute', - left: '16px', - height: '44px', + left: '0', + top: '23px', + height: '42px', marginRight: '10px', + borderColor: color.accentMedium, + borderStyle: 'solid', + borderWidth: '1px', + borderRadius: '100%', '@media (max-width: 760px)': { - left: '4px', + left: '5px', }, }, headerStyles: { borderBottomColor: color.accentLight, borderBottomStyle: 'solid', borderBottomWidth: '1px', - height: '67px', - marginBottom: padding.half, - marginRight: padding.medium, + height: '62px', + cursor: 'pointer', paddingLeft: '54px', + paddingTop: '26px', + marginRight: '16px', + position: 'relative', + }, + orgChevronStyles: { + color: color.accentMedium, + fontSize: '12px', + position: 'absolute', + top: '50px', + right: '35px', + '@media (max-width: 760px)': { + top: 'auto', + left: '0', + right: '0', + bottom: '6px', + textAlign: 'center', + display: 'block', + }, }, iconStyles: { position: 'relative', @@ -28,7 +50,9 @@ const componentStyles = { top: '4px', left: 0, '@media (max-width: 760px)': { - left: '5px', + display: 'block', + textAlign: 'center', + marginRight: 0, }, }, navItemBeforeStyles: { @@ -36,8 +60,8 @@ const componentStyles = { width: '6px', height: '50px', position: 'absolute', - left: '-24px', - top: '2px', + left: '-16px', + top: 0, bottom: 0, backgroundColor: '#9a61c6', '@media (max-width: 760px)': { @@ -60,10 +84,11 @@ const componentStyles = { const activeStyles = { color: color.brand, borderBottom: 'none', - transition: 'none', + ':hover': { + color: color.brandDark, + }, '@media (max-width: 760px)': { - borderBottom: '8px solid #9a61c6', - textAlign: 'center', + borderBottom: '6px solid #9a61c6', }, }; @@ -72,12 +97,13 @@ const componentStyles = { position: 'relative', color: color.textLight, cursor: 'pointer', - fontSize: font.small, + fontSize: '13px', + letterSpacing: '0.5px', textTransform: 'uppercase', paddingTop: padding.half, - transition: 'all 0.2s ease-in-out', - '@media (max-width: 760px)': { - textAlign: 'center', + transition: 'color 0.2s ease-in-out', + ':hover': { + color: color.textDark, }, }; @@ -122,25 +148,24 @@ const componentStyles = { bottom: 0, boxShadow: '2px 0 8px 0 rgba(0, 0, 0, 0.1)', left: 0, - paddingLeft: padding.large, - paddingTop: padding.large, + paddingLeft: '16px', position: 'fixed', top: 0, - width: '216px', + width: '223px', '@media (max-width: 760px)': { paddingLeft: 0, width: '54px', }, }, orgNameStyles: { - fontSize: font.medium, + fontSize: '16px', letterSpacing: '0.5px', margin: 0, overFlow: 'hidden', padding: 0, position: 'relative', textOverflow: 'ellipsis', - top: '3px', + top: '1px', whiteSpace: 'nowrap', '@media (max-width: 760px)': { display: 'none', @@ -167,8 +192,12 @@ const componentStyles = { }, subItemStyles: (active) => { const activeStyles = { + fontSize: '13px', fontWeight: font.weight.bold, opacity: '1', + ':hover': { + opacity: '1.0', + }, }; const baseStyles = { @@ -184,6 +213,9 @@ const componentStyles = { position: 'relative', textTransform: 'none', transition: 'all 0.2s ease-in-out', + ':hover': { + opacity: '0.75', + }, }; if (active) { @@ -195,54 +227,62 @@ const componentStyles = { return baseStyles; }, - subItemsStyles: (expanded) => { - return { - backgroundColor: color.brand, - boxShadow: 'inset 0 5px 8px 0 rgba(0, 0, 0, 0.12), inset 0 -5px 8px 0 rgba(0, 0, 0, 0.12)', - marginBottom: 0, - marginRight: 0, - minHeight: '87px', - paddingBottom: padding.half, - paddingTop: padding.half, - marginLeft: '-24px', - marginTop: padding.medium, - transition: 'width 0.1s ease-in-out', - '@media (max-width: 760px)': { - bottom: '-8px', - left: '54px', - marginLeft: 0, - position: 'absolute', - width: expanded ? '251px' : '18px', - }, - }; + subItemsStyles: { + backgroundColor: color.brand, + boxShadow: 'inset 0 5px 8px 0 rgba(0, 0, 0, 0.12), inset 0 -5px 8px 0 rgba(0, 0, 0, 0.12)', + marginRight: 0, + marginBottom: '6px', + paddingBottom: '3px', + paddingTop: '3px', + marginLeft: '-16px', + position: 'relative', + top: '10px', + transition: 'width 0.1s ease-in-out', + '@media (max-width: 760px)': { + minHeight: '84px', + borderTopRightRadius: '3px', + borderBottomRightRadius: '3px', + boxShadow: '2px 2px 8px rgba(0,0,0,0.1)', + bottom: '-8px', + left: '54px', + marginLeft: 0, + position: 'absolute', + }, }, subItemListStyles: (expanded) => { return { listStyle: 'none', + paddingLeft: '16px', '@media (max-width: 760px)': { borderRight: '1px solid rgba(0,0,0,0.16)', display: expanded ? 'inline-block' : 'none', padding: 0, textAlign: 'left', - width: '211px', + width: '166px', }, }; }, collapseSubItemsWrapper: { position: 'absolute', - right: '3px', - top: '41%', + right: '4px', + top: '0', + bottom: '0', + lineHeight: '95px', + color: '#fff', + '@media (min-width: 761px)': { display: 'none', }, }, usernameStyles: { position: 'relative', - top: '3px', display: 'inline-block', margin: 0, padding: 0, - fontSize: font.small, + top: '-3px', + left: '4px', + fontSize: '13px', + letterSpacing: '0.6px', textTransform: 'uppercase', '@media (max-width: 760px)': { display: 'none', @@ -257,10 +297,8 @@ const componentStyles = { borderRadius: border.radius.circle, display: 'inline-block', height: size, - left: '1px', marginRight: '6px', position: 'relative', - top: '6px', width: size, '@media (max-width: 760px)': { display: 'none', diff --git a/frontend/components/SidePanel/styles.scss b/frontend/components/SidePanel/styles.scss new file mode 100644 index 0000000000..8eb4c9d775 --- /dev/null +++ b/frontend/components/SidePanel/styles.scss @@ -0,0 +1,8 @@ +@media (max-width: 760px) { + .sub-nav { + width: 22px; + } + .sub-nav--expanded { + width: 188px; + } +} diff --git a/frontend/components/StackedWhiteBoxes/StackedWhiteBoxes.jsx b/frontend/components/StackedWhiteBoxes/StackedWhiteBoxes.jsx index 9080a57f2c..a16c604585 100644 --- a/frontend/components/StackedWhiteBoxes/StackedWhiteBoxes.jsx +++ b/frontend/components/StackedWhiteBoxes/StackedWhiteBoxes.jsx @@ -24,7 +24,7 @@ class StackedWhiteBoxes extends Component { return (
      - x +
      ); } diff --git a/frontend/components/StackedWhiteBoxes/styles.js b/frontend/components/StackedWhiteBoxes/styles.js index e92bfb2ce7..f8a5d2bd7e 100644 --- a/frontend/components/StackedWhiteBoxes/styles.js +++ b/frontend/components/StackedWhiteBoxes/styles.js @@ -4,16 +4,15 @@ const { border, color, font, padding } = styles; export default { boxStyles: { - alignItems: 'center', backgroundColor: color.white, - borderTopLeftRadius: border.radius.base, - borderTopRightRadius: border.radius.base, + borderRadius: border.radius.base, boxShadow: border.shadow.blur, + minHeight: '370px', boxSizing: 'border-box', - display: 'flex', - flexDirection: 'column', - padding: padding.base, - width: '522px', + padding: '30px', + width: '524px', + position: 'relative', + fontWeight: 300, }, containerStyles: { alignItems: 'center', @@ -24,6 +23,10 @@ export default { exStyles: { color: color.lightGrey, textDecoration: 'none', + position: 'absolute', + top: '30px', + right: '30px', + fontWeight: 'bold', }, exWrapperStyles: { textAlign: 'right', @@ -53,6 +56,8 @@ export default { textStyles: { color: color.purpleGrey, fontSize: font.medium, + lineHeight: '30px', + letterSpacing: '0.64px', }, smallTabStyles: { backgroundColor: color.white, diff --git a/frontend/components/buttons/GradientButton/styles.js b/frontend/components/buttons/GradientButton/styles.js index 9df7f50698..08786120c8 100644 --- a/frontend/components/buttons/GradientButton/styles.js +++ b/frontend/components/buttons/GradientButton/styles.js @@ -16,7 +16,6 @@ export default { fontSize: font.large, fontWeight: '300', letterSpacing: '4px', - marginTop: padding.base, padding: padding.medium, position: 'relative', textTransform: 'uppercase', diff --git a/frontend/components/forms/ForgotPasswordForm/ForgotPasswordForm.jsx b/frontend/components/forms/ForgotPasswordForm/ForgotPasswordForm.jsx index f8ce0c35f2..dc91d7d348 100644 --- a/frontend/components/forms/ForgotPasswordForm/ForgotPasswordForm.jsx +++ b/frontend/components/forms/ForgotPasswordForm/ForgotPasswordForm.jsx @@ -86,7 +86,7 @@ class ForgotPasswordForm extends Component { render () { const { error: serverError } = this.props; const { errors: clientErrors } = this.state; - const { formStyles, inputStyles, submitButtonStyles } = componentStyles; + const { formStyles, submitButtonContainerStyles, submitButtonStyles } = componentStyles; const { onFormSubmit, onInputFieldChange } = this; return ( @@ -94,17 +94,18 @@ class ForgotPasswordForm extends Component { - +
      + +
      ); } diff --git a/frontend/components/forms/ForgotPasswordForm/styles.js b/frontend/components/forms/ForgotPasswordForm/styles.js index 3c379eebdb..2db5632f3c 100644 --- a/frontend/components/forms/ForgotPasswordForm/styles.js +++ b/frontend/components/forms/ForgotPasswordForm/styles.js @@ -2,7 +2,15 @@ export default { formStyles: { width: '100%', }, - inputStyles: { - width: '100%', + submitButtonStyles: { + ':active': { + boxShadow: '0 1px 0 #734893', + }, + }, + submitButtonContainerStyles: { + position: 'absolute', + bottom: '30px', + left: '30px', + right: '30px', }, }; diff --git a/frontend/components/forms/LoginForm/LoginForm.jsx b/frontend/components/forms/LoginForm/LoginForm.jsx index 30c56d9c8f..1fddd1941d 100644 --- a/frontend/components/forms/LoginForm/LoginForm.jsx +++ b/frontend/components/forms/LoginForm/LoginForm.jsx @@ -140,14 +140,14 @@ class LoginForm extends Component { { - const { error } = this.props; - const { value } = this.state; - - if (error) return 'error'; - - if (value) return 'colored'; - - return 'default'; - } - renderHeading = () => { const { error, placeholder } = this.props; const { value } = this.state; @@ -70,9 +59,9 @@ class InputFieldWithIcon extends Component { render () { const { error, iconName, name, placeholder, style, type } = this.props; - const { containerStyles, iconStyles, inputErrorStyles, inputStyles } = componentStyles; + const { containerStyles, iconStyles, iconErrorStyles, inputErrorStyles, inputStyles } = componentStyles; const { value } = this.state; - const { iconVariant, onInputChange } = this; + const { onInputChange } = this; return (
      @@ -80,12 +69,13 @@ class InputFieldWithIcon extends Component { { this.input = r; }} - style={[inputStyles(value), inputErrorStyles(error), style]} + style={[inputStyles(value, type), inputErrorStyles(error), style]} type={type} /> - +
      ); } diff --git a/frontend/components/forms/fields/InputFieldWithIcon/styles.js b/frontend/components/forms/fields/InputFieldWithIcon/styles.js index 5ddf8f47cd..27237b3de8 100644 --- a/frontend/components/forms/fields/InputFieldWithIcon/styles.js +++ b/frontend/components/forms/fields/InputFieldWithIcon/styles.js @@ -6,16 +6,38 @@ export default { containerStyles: { marginTop: padding.base, position: 'relative', + width: '100%', }, errorStyles: { color: color.alert, fontSize: font.small, textTransform: 'lowercase', }, - iconStyles: { - position: 'absolute', - right: '6px', - top: '29px', + iconStyles: (value) => { + const baseStyles = { + position: 'absolute', + right: '6px', + top: '28px', + fontSize: '20px', + color: color.accentText, + }; + if (value) { + return { + ...baseStyles, + color: color.brand, + }; + } + + return baseStyles; + }, + + iconErrorStyles: (error) => { + if (error) { + return { + color: color.alert, + }; + } + return false; }, inputErrorStyles: (error) => { if (error) { @@ -26,21 +48,35 @@ export default { return {}; }, - inputStyles: (value) => { + inputStyles: (value, type) => { const baseStyles = { borderLeft: 'none', borderRight: 'none', borderTop: 'none', - borderBottomWidth: '1px', + borderBottomWidth: '2px', + fontSize: '20px', borderBottomStyle: 'solid', - borderBottomColor: color.brand, + borderBottomColor: color.brandUltralight, color: color.accentText, - width: '378px', + paddingRight: '30px', + opacity: '1', + textIndent: '1px', + position: 'relative', + width: '100%', + boxSizing: 'border-box', ':focus': { outline: 'none', }, }; + if (type === 'password' && value) { + return { + ...baseStyles, + letterSpacing: '7px', + color: color.textUltradark, + }; + } + if (value) { return { ...baseStyles, diff --git a/frontend/index.jsx b/frontend/index.jsx index 67f250e226..56a32170d2 100644 --- a/frontend/index.jsx +++ b/frontend/index.jsx @@ -1,5 +1,6 @@ import ReactDOM from 'react-dom'; import routes from './router'; +import './index.scss'; if (typeof window !== 'undefined') { const { document } = global; diff --git a/frontend/index.scss b/frontend/index.scss new file mode 100644 index 0000000000..1399fd12e5 --- /dev/null +++ b/frontend/index.scss @@ -0,0 +1,3 @@ +@import "stylesheets/fonts.scss"; +@import "stylesheets/icons.scss"; +@import "stylesheets/forms.scss"; diff --git a/frontend/pages/LoginSuccessfulPage/LoginSuccessfulPage.jsx b/frontend/pages/LoginSuccessfulPage/LoginSuccessfulPage.jsx index 8f7166a1f7..7c7a9438e8 100644 --- a/frontend/pages/LoginSuccessfulPage/LoginSuccessfulPage.jsx +++ b/frontend/pages/LoginSuccessfulPage/LoginSuccessfulPage.jsx @@ -6,13 +6,11 @@ import componentStyles from './styles'; import Icon from '../../components/icons/Icon'; import paths from '../../router/paths'; -const COUNTDOWN_INTERVAL = 1000; -const REDIRECT_TIME = 3000; +const REDIRECT_TIME = 1200; class LoginSuccessfulPage extends Component { static propTypes = { dispatch: PropTypes.func.isRequired, - user: PropTypes.object, }; constructor (props) { @@ -27,42 +25,23 @@ class LoginSuccessfulPage extends Component { this.startRedirectCountdown(); } - componentWillUnmount () { - const { interval } = this; - - if (interval) clearInterval(interval); - } - startRedirectCountdown = () => { const { dispatch } = this.props; const { HOME } = paths; + const { redirectTime } = this.state; - this.interval = setInterval(() => { - const { redirectTime } = this.state; - - if (redirectTime > 0) { - this.setState({ - redirectTime: redirectTime - COUNTDOWN_INTERVAL, - }); - - return false; - } - + setTimeout(() => { return dispatch(push(HOME)); - }, COUNTDOWN_INTERVAL); + }, redirectTime); } render () { const { loginSuccessStyles, subtextStyles, whiteBoxStyles } = componentStyles; - const { redirectTime } = this.state; - const secondsToRedirect = redirectTime / 1000; - return (

      Login successful

      -

      Hold on to your butts.

      -

      redirecting in {secondsToRedirect}

      +

      hold on to your butts...

      ); } diff --git a/assets/stylesheets/fonts.css b/frontend/stylesheets/fonts.scss similarity index 100% rename from assets/stylesheets/fonts.css rename to frontend/stylesheets/fonts.scss diff --git a/frontend/stylesheets/forms.scss b/frontend/stylesheets/forms.scss new file mode 100644 index 0000000000..8b3c16a9a0 --- /dev/null +++ b/frontend/stylesheets/forms.scss @@ -0,0 +1,4 @@ +.input-with-icon::placeholder { + color: #A8B1CD; + opacity: 1; +} diff --git a/assets/stylesheets/icons.css b/frontend/stylesheets/icons.scss similarity index 100% rename from assets/stylesheets/icons.css rename to frontend/stylesheets/icons.scss diff --git a/frontend/templates/react.tmpl b/frontend/templates/react.tmpl index 185751cd75..84252d3469 100644 --- a/frontend/templates/react.tmpl +++ b/frontend/templates/react.tmpl @@ -2,8 +2,7 @@ - - + Kolide diff --git a/package.json b/package.json index eeb1138081..734ebc3ad2 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "jsdom": "^9.5.0", "lodash": "^4.3.0", "nock": "^8.0.0", + "node-sass": "^3.10.0", "postcss-functions": "^2.1.0", "postcss-loader": "^0.8.0", "precss": "^1.4.0", @@ -49,6 +50,7 @@ "redux-mock-store": "^1.2.0", "redux-thunk": "^2.1.0", "require-hacker": "^2.1.4", + "sass-loader": "^4.0.2", "style-loader": "^0.13.0", "stylus-loader": "1.5.1", "url-loader": "^0.5.7", diff --git a/webpack.config.js b/webpack.config.js index c52d129216..13f0d493e4 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,8 +6,9 @@ var autoprefixer = require('autoprefixer'); var ExtractTextPlugin = require("extract-text-webpack-plugin"); var plugins = [ - new webpack.NoErrorsPlugin(), - new webpack.optimize.DedupePlugin(), + new webpack.NoErrorsPlugin(), + new webpack.optimize.DedupePlugin(), + new ExtractTextPlugin("bundle.css", {allChunks: false}) ]; if (process.env.NODE_ENV === 'production') { @@ -39,6 +40,10 @@ var config = { {test: /\.(png|gif)$/, loader: 'url-loader?name=[name]@[hash].[ext]&limit=6000'}, {test: /\.(pdf|ico|jpg|svg|eot|otf|woff|ttf|mp4|webm)$/, loader: 'file-loader?name=[name]@[hash].[ext]'}, {test: /\.json$/, loader: 'json-loader'}, + { + test: /\.scss$/, + loader: ExtractTextPlugin.extract("style-loader", "css-loader!autoprefixer-loader!sass-loader") + }, { test: /\.jsx?$/, include: path.join(repo, 'frontend'),