diff --git a/.stylelintrc b/.stylelintrc
index 8d41721..0df797a 100644
--- a/.stylelintrc
+++ b/.stylelintrc
@@ -1,3 +1,8 @@
{
- "extends": ["@hellroot/stylelint-config"]
+ "extends": ["@hellroot/stylelint-config"],
+ "rules": {
+ "selector-pseudo-class-no-unknown": [true, {
+ "ignorePseudoClasses": ["global"]
+ }]
+ }
}
diff --git a/.travis.yml b/.travis.yml
index 93c4d63..8e325a0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,5 +2,4 @@ language: node_js
node_js:
- "10"
script:
- - npm run test
- npm run lint
diff --git a/package-lock.json b/package-lock.json
index 937a670..ca2f398 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -949,7 +949,6 @@
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.3.1.tgz",
"integrity": "sha512-7jGW8ppV0ant637pIqAcFfQDDH1orEPGJb8aXfUozuCU3QqX7rX4DA8iwrbPrR1hcH0FTTHz47yQnk+bl5xHQA==",
- "dev": true,
"requires": {
"regenerator-runtime": "^0.12.0"
},
@@ -957,8 +956,7 @@
"regenerator-runtime": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",
- "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==",
- "dev": true
+ "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg=="
}
}
},
@@ -1233,6 +1231,19 @@
"@types/node": "*"
}
},
+ "@types/jest": {
+ "version": "24.0.12",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.12.tgz",
+ "integrity": "sha512-60sjqMhat7i7XntZckcSGV8iREJyXXI6yFHZkSZvCPUeOnEJ/VP1rU/WpEWQ56mvoh8NhC+sfKAuJRTyGtCOow==",
+ "requires": {
+ "@types/jest-diff": "*"
+ }
+ },
+ "@types/jest-diff": {
+ "version": "20.0.1",
+ "resolved": "https://registry.npmjs.org/@types/jest-diff/-/jest-diff-20.0.1.tgz",
+ "integrity": "sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA=="
+ },
"@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@@ -1246,10 +1257,14 @@
"dev": true
},
"@types/node": {
- "version": "11.13.5",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.5.tgz",
- "integrity": "sha512-/OMMBnjVtDuwX1tg2pkYVSqRIDSmNTnvVvmvP/2xiMAAWf4a5+JozrApCrO4WCAILmXVxfNoQ3E+0HJbNpFVGg==",
- "dev": true
+ "version": "11.13.8",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.8.tgz",
+ "integrity": "sha512-szA3x/3miL90ZJxUCzx9haNbK5/zmPieGraZEe4WI+3srN0eGLiT22NXeMHmyhNEopn+IrxqMc7wdVwvPl8meg=="
+ },
+ "@types/prop-types": {
+ "version": "15.7.1",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.1.tgz",
+ "integrity": "sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg=="
},
"@types/q": {
"version": "1.5.2",
@@ -1257,6 +1272,33 @@
"integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==",
"dev": true
},
+ "@types/react": {
+ "version": "16.8.15",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.15.tgz",
+ "integrity": "sha512-dMhzw1rWK+wwJWvPp5Pk12ksSrm/z/C/+lOQbMZ7YfDQYnJ02bc0wtg4EJD9qrFhuxFrf/ywNgwTboucobJqQg==",
+ "requires": {
+ "@types/prop-types": "*",
+ "csstype": "^2.2.0"
+ }
+ },
+ "@types/react-dom": {
+ "version": "16.8.4",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.8.4.tgz",
+ "integrity": "sha512-eIRpEW73DCzPIMaNBDP5pPIpK1KXyZwNgfxiVagb5iGiz6da+9A5hslSX6GAQKdO7SayVCS/Fr2kjqprgAvkfA==",
+ "requires": {
+ "@types/react": "*"
+ }
+ },
+ "@types/react-virtualized": {
+ "version": "9.21.1",
+ "resolved": "https://registry.npmjs.org/@types/react-virtualized/-/react-virtualized-9.21.1.tgz",
+ "integrity": "sha512-BwWXk6Vy+YuWbc2jZsmm0fn8bglPUpqUWPH/JUUBfvfKfL2nDvvmCiauyxMCWrxZMVBbkxaUuP82SviaDv0wGw==",
+ "dev": true,
+ "requires": {
+ "@types/prop-types": "*",
+ "@types/react": "*"
+ }
+ },
"@types/tapable": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.2.tgz",
@@ -2652,7 +2694,6 @@
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
"integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
- "dev": true,
"requires": {
"core-js": "^2.4.0",
"regenerator-runtime": "^0.11.0"
@@ -2661,8 +2702,7 @@
"core-js": {
"version": "2.6.5",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz",
- "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==",
- "dev": true
+ "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A=="
}
}
},
@@ -4085,6 +4125,11 @@
}
}
},
+ "classnames": {
+ "version": "2.2.6",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
+ "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
+ },
"clean-css": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz",
@@ -4890,6 +4935,11 @@
"cssom": "0.3.x"
}
},
+ "csstype": {
+ "version": "2.6.4",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.4.tgz",
+ "integrity": "sha512-lAJUJP3M6HxFXbqtGRc0iZrdyeN+WzOWeY0q/VnFzI+kqVrYIzC7bWlKqCW7oCIdzoPkvfp82EVvrTlQ8zsWQg=="
+ },
"currently-unhandled": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
@@ -5317,6 +5367,14 @@
"utila": "~0.4"
}
},
+ "dom-helpers": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
+ "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
+ "requires": {
+ "@babel/runtime": "^7.1.2"
+ }
+ },
"dom-serializer": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz",
@@ -13557,6 +13615,11 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz",
"integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA=="
},
+ "react-lifecycles-compat": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
+ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
+ },
"react-scripts": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-2.1.8.tgz",
@@ -13705,6 +13768,19 @@
}
}
},
+ "react-virtualized": {
+ "version": "9.21.0",
+ "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.21.0.tgz",
+ "integrity": "sha512-duKD2HvO33mqld4EtQKm9H9H0p+xce1c++2D5xn59Ma7P8VT7CprfAe5hwjd1OGkyhqzOZiTMlTal7LxjH5yBQ==",
+ "requires": {
+ "babel-runtime": "^6.26.0",
+ "classnames": "^2.2.3",
+ "dom-helpers": "^2.4.0 || ^3.0.0",
+ "loose-envify": "^1.3.0",
+ "prop-types": "^15.6.0",
+ "react-lifecycles-compat": "^3.0.4"
+ }
+ },
"read-pkg": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
@@ -14109,8 +14185,7 @@
"regenerator-runtime": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
- "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
- "dev": true
+ "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
},
"regenerator-transform": {
"version": "0.13.4",
diff --git a/package.json b/package.json
index 9ede333..dac6f16 100644
--- a/package.json
+++ b/package.json
@@ -9,12 +9,18 @@
"not op_mini all"
],
"dependencies": {
+ "@types/jest": "24.0.12",
+ "@types/node": "11.13.8",
+ "@types/react": "16.8.15",
+ "@types/react-dom": "16.8.4",
"react": "16.8.6",
- "react-dom": "16.8.6"
+ "react-dom": "16.8.6",
+ "react-virtualized": "9.21.0"
},
"devDependencies": {
"@hellroot/eslint-config": "1.8.0",
"@hellroot/stylelint-config": "1.1.0",
+ "@types/react-virtualized": "9.21.1",
"eslint": "5.12.0",
"npm-run-all": "4.1.5",
"prettier": "1.17.0",
diff --git a/public/favicon.ico b/public/favicon.ico
deleted file mode 100644
index a11777c..0000000
Binary files a/public/favicon.ico and /dev/null differ
diff --git a/public/index.html b/public/index.html
index 9a8ef8f..4f4d4b1 100644
--- a/public/index.html
+++ b/public/index.html
@@ -2,16 +2,15 @@
-
+
- React App
+ Яндекс.Почта
-
diff --git a/public/ya-mail-favicon.png b/public/ya-mail-favicon.png
new file mode 100644
index 0000000..555ec0b
Binary files /dev/null and b/public/ya-mail-favicon.png differ
diff --git a/src/app/app.css b/src/app/app.css
deleted file mode 100644
index 1c4d511..0000000
--- a/src/app/app.css
+++ /dev/null
@@ -1,27 +0,0 @@
-.app {
- text-align: center;
-}
-
-.app-header {
- display: flex;
- min-height: 100vh;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- background-color: #282c34;
- color: #fff;
- font-size: calc(10px + 2vmin);
-}
-
-.app-link {
- color: #61dafb;
-}
-
-@keyframes app-logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
diff --git a/src/app/app.jsx b/src/app/app.jsx
deleted file mode 100644
index f759eed..0000000
--- a/src/app/app.jsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import React, { Component } from 'react';
-
-import './app.css';
-
-export class App extends Component {
- render() {
- return (
-
- );
- }
-}
diff --git a/src/app/app.module.css b/src/app/app.module.css
new file mode 100644
index 0000000..789c862
--- /dev/null
+++ b/src/app/app.module.css
@@ -0,0 +1,8 @@
+.app {
+ width: available;
+ min-width: 950px;
+ min-height: 100%;
+ margin-right: 22px;
+ margin-left: 22px;
+ font-family: 'Helvetica Neue', sans-serif;
+}
diff --git a/src/app/app.test.jsx b/src/app/app.test.jsx
deleted file mode 100644
index 81be6fa..0000000
--- a/src/app/app.test.jsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-
-import { App } from './app';
-
-it('renders without crashing', () => {
- const div = document.createElement('div');
-
- ReactDOM.render(, div);
- ReactDOM.unmountComponentAtNode(div);
-});
diff --git a/src/app/app.tsx b/src/app/app.tsx
new file mode 100644
index 0000000..58d7061
--- /dev/null
+++ b/src/app/app.tsx
@@ -0,0 +1,157 @@
+import React, { Component } from 'react';
+
+import styles from './app.module.css';
+
+import Header from '../header';
+import MainBlock from '../main-block';
+
+export interface MessageInterface {
+ id: number,
+ senderName: String,
+ senderLogo: String,
+ subject: String,
+ date: String,
+ hiddenText: String,
+ selected: boolean,
+ shrink: boolean,
+ unshrink: boolean
+}
+
+interface State {
+ messagesList: MessageInterface[],
+ messageIsOpen: boolean
+}
+
+export class App extends Component {
+ private messagesPerPage: number;
+ private timeoutUpper: number;
+ private timeoutLower: number;
+
+ private senders: String[];
+ private subjects: String[];
+ private texts: String[];
+ private months: String[];
+
+ public state: State;
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+ this.newMail = this.newMail.bind(this);
+ this.createAndRandom = this.createAndRandom.bind(this);
+ this.newRandomMessage = this.newRandomMessage.bind(this);
+ this.buildNewMessage = this.buildNewMessage.bind(this);
+ this.updateList = this.updateList.bind(this);
+
+ this.messagesPerPage = 1000;
+
+ this.senders = ['Петя', 'Вася', 'Маша'];
+ this.subjects = ['Привет из России', 'Hello from England', 'Bonjour de France'];
+ this.texts = ['Привет!', 'Hello!', 'Bonjour!'];
+ this.months = [
+ 'январь',
+ 'февраль',
+ 'март',
+ 'апрель',
+ 'май',
+ 'июнь',
+ 'июль',
+ 'август',
+ 'сентябрь',
+ 'октябрь',
+ 'ноябрь',
+ 'декабрь'
+ ];
+ this.timeoutUpper = 10 * 60 * 1000;
+ this.timeoutLower = 5 * 60 * 1000;
+
+ this.state = {
+ messagesList: [],
+ messageIsOpen: false
+ };
+ }
+
+ componentDidMount() {
+ const thisHolder = this;
+ thisHolder.createAndRandom();
+ }
+
+ createAndRandom() {
+ this.newMail();
+ this.newRandomMessage();
+ }
+
+ newRandomMessage() {
+ setTimeout(
+ this.createAndRandom,
+ Math.random() * (this.timeoutUpper - this.timeoutLower) + this.timeoutLower
+ );
+ }
+
+ newMail() {
+ this.setState((prevState: State) => {
+ const newMessagesList = prevState.messagesList;
+ const newMessage = this.buildNewMessage();
+
+ newMessagesList.unshift(newMessage);
+
+ setTimeout(() => {
+ newMessage.unshrink = true;
+ this.setState({
+ messagesList: newMessagesList
+ });
+ }, 50);
+
+ return {
+ messagesList: newMessagesList
+ };
+ });
+ }
+
+ buildNewMessage(): MessageInterface {
+ const currentDate = new Date();
+
+ const id = currentDate.getTime();
+ const langInd = Math.floor(Math.random() * this.senders.length);
+ const hiddenText = this.texts[langInd];
+
+ const monthInd = currentDate.getMonth();
+ const month = this.months[monthInd];
+ const day = currentDate.getDate();
+
+ const senderName = this.senders[Math.floor(Math.random() * this.senders.length)];
+ return {
+ id,
+ senderName,
+ senderLogo: senderName[0],
+ subject: this.subjects[langInd],
+ date: `${day} ${month.substr(0, 3)}`,
+ hiddenText,
+ selected: false,
+ shrink: false,
+ unshrink: false
+ };
+ }
+
+ updateList(newList: MessageInterface[]) {
+ this.setState(() => {
+ return {
+ messagesList: newList
+ }
+ })
+ }
+
+ render() {
+ return (
+
+
+ );
+ }
+}
+
+export default App;
diff --git a/src/app/index.js b/src/app/index.ts
similarity index 100%
rename from src/app/index.js
rename to src/app/index.ts
diff --git a/src/header/header.module.css b/src/header/header.module.css
new file mode 100644
index 0000000..74380dd
--- /dev/null
+++ b/src/header/header.module.css
@@ -0,0 +1,3 @@
+.mail-page-header {
+ margin-top: 11px;
+}
diff --git a/src/header/header.tsx b/src/header/header.tsx
new file mode 100644
index 0000000..963fdf5
--- /dev/null
+++ b/src/header/header.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+
+import styles from './header.module.css';
+
+import Menu from './menu/menu';
+import YandexServiceLogo from './yandex-service-logo/yandex-service-logo';
+import SearchBar from './search-bar/search-bar';
+import MessageCreator from './message-creator/message-creator';
+
+interface InjectedProps {
+ newMailFunction: () => void;
+}
+
+class Header extends React.Component {
+ render() {
+ return (
+
+ );
+ }
+}
+
+export default Header;
diff --git a/src/header/index.ts b/src/header/index.ts
new file mode 100644
index 0000000..0d3f295
--- /dev/null
+++ b/src/header/index.ts
@@ -0,0 +1,2 @@
+import Header from './header'
+export default Header
diff --git a/src/header/menu/menu.module.css b/src/header/menu/menu.module.css
new file mode 100644
index 0000000..b777c25
--- /dev/null
+++ b/src/header/menu/menu.module.css
@@ -0,0 +1,20 @@
+.header__menu {
+ display: inline-block;
+ width: 20px;
+ height: 17px;
+ margin-top: -5px;
+ margin-right: 10px;
+ cursor: pointer;
+ outline: none;
+ vertical-align: middle;
+}
+
+.header__menu:focus {
+ outline: 2px solid #d9f7ff;
+}
+
+.menu__stripe {
+ height: 2px;
+ margin-top: 4px;
+ background-color: #000;
+}
diff --git a/src/header/menu/menu.tsx b/src/header/menu/menu.tsx
new file mode 100644
index 0000000..e9698cc
--- /dev/null
+++ b/src/header/menu/menu.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+
+import styles from './menu.module.css';
+
+function Menu() {
+ return (
+ {
+ click.preventDefault();
+ }}>
+
+
+
+
+ );
+}
+
+export default Menu;
diff --git a/src/header/message-creator/message-creator.module.css b/src/header/message-creator/message-creator.module.css
new file mode 100644
index 0000000..1c62b55
--- /dev/null
+++ b/src/header/message-creator/message-creator.module.css
@@ -0,0 +1,5 @@
+.header__message-creator {
+ cursor: pointer;
+ float: right;
+ opacity: 0.5;
+}
diff --git a/src/header/message-creator/message-creator.tsx b/src/header/message-creator/message-creator.tsx
new file mode 100644
index 0000000..4eba7b7
--- /dev/null
+++ b/src/header/message-creator/message-creator.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+
+import styles from './message-creator.module.css';
+
+interface InjectedProps {
+ newMailFunction: () => void;
+}
+
+class MessageCreator extends React.Component {
+ render() {
+ return (
+
+ );
+ }
+}
+
+export default MessageCreator;
diff --git a/src/header/search-bar/search-bar.module.css b/src/header/search-bar/search-bar.module.css
new file mode 100644
index 0000000..8973fb1
--- /dev/null
+++ b/src/header/search-bar/search-bar.module.css
@@ -0,0 +1,25 @@
+.header__search-bar {
+ display: inline-block;
+ width: 35%;
+ height: 34px;
+ padding-right: 10px;
+ padding-left: 16px;
+ margin-left: 12.5%;
+
+ border-width: 0;
+ background: rgba(242, 244, 247, 1);
+ box-shadow: inset 0 0 0 1px rgba(217, 219, 222, 1);
+ color: #000;
+
+ font: 15px 'Helvetica Neue', sans-serif;
+ line-height: 2;
+ outline: none;
+}
+
+.header__search-bar:focus {
+ outline: 2px solid #d9f7ff;
+}
+
+.header__search-bar::placeholder {
+ color: #727477;
+}
diff --git a/src/header/search-bar/search-bar.tsx b/src/header/search-bar/search-bar.tsx
new file mode 100644
index 0000000..5132690
--- /dev/null
+++ b/src/header/search-bar/search-bar.tsx
@@ -0,0 +1,13 @@
+import React from 'react';
+
+import styles from './search-bar.module.css';
+
+class SearchBar extends React.Component {
+ render() {
+ return ;
+ }
+}
+
+export default SearchBar;
diff --git a/src/header/yandex-service-logo/yandex-service-logo.module.css b/src/header/yandex-service-logo/yandex-service-logo.module.css
new file mode 100644
index 0000000..a031172
--- /dev/null
+++ b/src/header/yandex-service-logo/yandex-service-logo.module.css
@@ -0,0 +1,21 @@
+.header__yandex-service-logo {
+ display: inline-block;
+ width: 200px;
+ height: 31px;
+ vertical-align: middle;
+}
+
+.yandex-service-logo__image-link {
+ display: inline-block;
+ cursor: pointer;
+ outline: none;
+}
+
+.yandex-service-logo__image-link:focus {
+ outline: 2px solid #d9f7ff;
+}
+
+.yandex-service-logo__image {
+ width: 153px;
+ height: 31px;
+}
diff --git a/src/header/yandex-service-logo/yandex-service-logo.tsx b/src/header/yandex-service-logo/yandex-service-logo.tsx
new file mode 100644
index 0000000..094372c
--- /dev/null
+++ b/src/header/yandex-service-logo/yandex-service-logo.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+
+import styles from './yandex-service-logo.module.css';
+
+import YandexMail from '../../resources/yandex-mail.png';
+
+class YandexServiceLogo extends React.Component {
+ render() {
+ return (
+
+ );
+ }
+}
+
+export default YandexServiceLogo;
diff --git a/src/index.css b/src/index.css
index 2b6e525..355b134 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,12 +1,7 @@
body {
+ width: 100%;
+ height: 100%;
padding: 0;
margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
- 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
+ background: #e5eaf0;
}
diff --git a/src/index.jsx b/src/index.jsx
index ffc72ee..f6073ef 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
-import { App } from './app';
+import { App } from './app/app';
import './index.css';
diff --git a/src/main-block/index.ts b/src/main-block/index.ts
new file mode 100644
index 0000000..f80c28e
--- /dev/null
+++ b/src/main-block/index.ts
@@ -0,0 +1,2 @@
+import MainBlock from './main-block'
+export default MainBlock
diff --git a/src/main-block/left-menu/folders-list/folders-list.module.css b/src/main-block/left-menu/folders-list/folders-list.module.css
new file mode 100644
index 0000000..dc5c20a
--- /dev/null
+++ b/src/main-block/left-menu/folders-list/folders-list.module.css
@@ -0,0 +1,30 @@
+.folders-list {
+ margin-top: 12px;
+}
+
+.selected {
+ background-color: #cdd6e4;
+ border-radius: 4px;
+ color: #555;
+ font-weight: 700;
+}
+
+.folders-list__folder {
+ overflow: hidden;
+ height: 14px;
+ padding: 5px 10px;
+ color: #707070;
+ font-size: 13px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.folders-list__folder:hover {
+ background-color: #d8e1ef;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+.selected:hover {
+ background-color: #cdd6e4;
+}
diff --git a/src/main-block/left-menu/folders-list/folders-list.tsx b/src/main-block/left-menu/folders-list/folders-list.tsx
new file mode 100644
index 0000000..cc15e94
--- /dev/null
+++ b/src/main-block/left-menu/folders-list/folders-list.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+
+import styles from './folders-list.module.css';
+
+class FoldersList extends React.Component{
+ render() {
+ return (
+
+
Входящие
+
Отправленные
+
Удаленные
+
Спам
+
Черновики
+
Создать папку
+
+ );
+ }
+}
+
+export default FoldersList;
diff --git a/src/main-block/left-menu/index.ts b/src/main-block/left-menu/index.ts
new file mode 100644
index 0000000..ee2e47f
--- /dev/null
+++ b/src/main-block/left-menu/index.ts
@@ -0,0 +1,2 @@
+import LeftMenu from './left-menu'
+export default LeftMenu
diff --git a/src/main-block/left-menu/left-menu.module.css b/src/main-block/left-menu/left-menu.module.css
new file mode 100644
index 0000000..d74d4e5
--- /dev/null
+++ b/src/main-block/left-menu/left-menu.module.css
@@ -0,0 +1,24 @@
+.main-block__left-menu {
+ display: inline-block;
+ width: 19.5%;
+ min-width: 150px;
+}
+
+.left-menu__write-letter {
+ overflow: hidden;
+ height: 32px;
+ padding: 0 10px;
+ background-color: #6287bd;
+ border-radius: 4px;
+ color: #fff;
+ font-size: 12px;
+ line-height: 32px;
+
+ text-align: center;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.left-menu__write-letter:hover {
+ cursor: pointer;
+}
diff --git a/src/main-block/left-menu/left-menu.tsx b/src/main-block/left-menu/left-menu.tsx
new file mode 100644
index 0000000..6a291bd
--- /dev/null
+++ b/src/main-block/left-menu/left-menu.tsx
@@ -0,0 +1,17 @@
+import React, { ReactComponentElement } from 'react';
+
+import styles from './left-menu.module.css';
+import FoldersList from './folders-list/folders-list';
+
+class LeftMenu extends React.Component {
+ render() {
+ return (
+
+ );
+ }
+}
+
+export default LeftMenu;
diff --git a/src/main-block/main-block.module.css b/src/main-block/main-block.module.css
new file mode 100644
index 0000000..7437726
--- /dev/null
+++ b/src/main-block/main-block.module.css
@@ -0,0 +1,5 @@
+.mail-page__main-block {
+ min-width: 800px;
+ min-height: 100%;
+ padding-top: 13px;
+}
diff --git a/src/main-block/main-block.tsx b/src/main-block/main-block.tsx
new file mode 100644
index 0000000..bcde187
--- /dev/null
+++ b/src/main-block/main-block.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+
+import styles from './main-block.module.css';
+import LeftMenu from './left-menu';
+import MessagesBlock from './messages-block';
+import { MessageInterface } from '../app';
+
+interface InjectedProps {
+ messagesList: MessageInterface[];
+ messagesPerPage: number;
+ updateList: (newList: MessageInterface[]) => void;
+}
+
+class MainBlock extends React.Component {
+ render() {
+ return (
+
+
+
+
+ );
+ }
+}
+
+export default MainBlock;
diff --git a/src/main-block/messages-block/footer/footer.module.css b/src/main-block/messages-block/footer/footer.module.css
new file mode 100644
index 0000000..5a40335
--- /dev/null
+++ b/src/main-block/messages-block/footer/footer.module.css
@@ -0,0 +1,35 @@
+.footer {
+ width: 100%;
+ height: 34px;
+
+ border-top: solid 1px #e2e2e2;
+}
+
+.footer__links-list {
+ display: inline;
+ padding: 5px 20px;
+ margin: 5px 0;
+ float: right;
+ font-family: 'Helvetica Neue', sans-serif;
+ font-size: 11px;
+}
+
+.link-container {
+ display: inline;
+}
+
+.footer-link {
+ display: inline-block;
+ overflow: hidden;
+ max-width: 200px;
+ margin-left: 20px;
+ color: #ccc;
+ cursor: pointer;
+ outline: none;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.footer-link:focus {
+ outline: 2px solid #d9f7ff;
+}
diff --git a/src/main-block/messages-block/footer/footer.tsx b/src/main-block/messages-block/footer/footer.tsx
new file mode 100644
index 0000000..945619e
--- /dev/null
+++ b/src/main-block/messages-block/footer/footer.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+
+import styles from './footer.module.css';
+
+class Footer extends React.Component{
+ render() {
+ return (
+
+ );
+ }
+}
+
+export default Footer;
diff --git a/src/main-block/messages-block/footer/index.ts b/src/main-block/messages-block/footer/index.ts
new file mode 100644
index 0000000..cd82817
--- /dev/null
+++ b/src/main-block/messages-block/footer/index.ts
@@ -0,0 +1,2 @@
+import Footer from './footer'
+export default Footer
diff --git a/src/main-block/messages-block/header/header.module.css b/src/main-block/messages-block/header/header.module.css
new file mode 100644
index 0000000..eb52409
--- /dev/null
+++ b/src/main-block/messages-block/header/header.module.css
@@ -0,0 +1,53 @@
+.messages-block-header {
+ overflow: auto;
+ height: 40px;
+
+ border-bottom: solid 1px #e2e2e2;
+ font-family: 'Helvetica Neue', sans-serif;
+ font-size: 13px;
+ white-space: nowrap;
+}
+
+.messages-block-header__action {
+ display: inline-block;
+ width: auto;
+
+ margin-left: 15px;
+ color: #ccc;
+ cursor: pointer;
+ font-weight: 500;
+ text-overflow: ellipsis;
+ vertical-align: middle;
+ white-space: nowrap;
+}
+
+.button-to-div {
+ padding: 0;
+
+ border: 0;
+ font-family: inherit;
+ font-size: 100%;
+ outline: none;
+}
+
+.button-to-div:focus {
+ outline: 2px solid #d9f7ff;
+}
+
+#check-all:global {
+ margin-top: 12px;
+ margin-right: 10px;
+ margin-left: 20px;
+}
+
+#check-all-label:global {
+ display: inline-block;
+ width: 46px;
+ height: 40px;
+ cursor: pointer;
+ vertical-align: middle;
+}
+
+.cursor-pointer {
+ cursor: pointer;
+}
diff --git a/src/main-block/messages-block/header/header.tsx b/src/main-block/messages-block/header/header.tsx
new file mode 100644
index 0000000..a711e17
--- /dev/null
+++ b/src/main-block/messages-block/header/header.tsx
@@ -0,0 +1,57 @@
+import React from 'react';
+
+import styles from './header.module.css';
+import checkboxStyle from '../messages-block.module.css'
+
+interface InjectedProps {
+ handleSelectAll: () => void;
+ deleteSelected: () => void;
+ selectAllCheckbox: boolean;
+}
+
+class Header extends React.Component {
+ render() {
+ return (
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default Header;
diff --git a/src/main-block/messages-block/header/index.ts b/src/main-block/messages-block/header/index.ts
new file mode 100644
index 0000000..0d3f295
--- /dev/null
+++ b/src/main-block/messages-block/header/index.ts
@@ -0,0 +1,2 @@
+import Header from './header'
+export default Header
diff --git a/src/main-block/messages-block/hidden-message/hidden-message.module.css b/src/main-block/messages-block/hidden-message/hidden-message.module.css
new file mode 100644
index 0000000..da7e406
--- /dev/null
+++ b/src/main-block/messages-block/hidden-message/hidden-message.module.css
@@ -0,0 +1,38 @@
+.hidden-message {
+ position: relative;
+ z-index: 1;
+ top: 0;
+
+ display: none;
+ min-height: 500px;
+
+ border-bottom: solid 1px #e2e2e2;
+ background-color: #fff;
+}
+
+.hidden-message__open {
+ display: block;
+}
+
+.hidden-message__closed {
+ display: none;
+}
+
+.hidden-message__content {
+ overflow: auto;
+ padding: 20px;
+ font-family: 'Helvetica Neue', sans-serif;
+ font-size: 14px;
+ white-space: normal;
+}
+
+.close-message {
+ margin-right: 5px;
+
+ border: 0;
+ color: #ccc;
+ cursor: pointer;
+ float: right;
+ font-size: 35px;
+ font-weight: 100;
+}
diff --git a/src/main-block/messages-block/hidden-message/hidden-message.tsx b/src/main-block/messages-block/hidden-message/hidden-message.tsx
new file mode 100644
index 0000000..4ce74c6
--- /dev/null
+++ b/src/main-block/messages-block/hidden-message/hidden-message.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+
+import styles from './hidden-message.module.css';
+
+interface InjectedProps {
+ closeMessage: () => void;
+ messageIsOpen: boolean;
+ hiddenMessageText: String;
+}
+
+class HiddenMessage extends React.Component {
+ render() {
+ const classAddition = this.props.messageIsOpen ? '__open' : '__closed';
+ return (
+
+
+
+
+ {this.props.hiddenMessageText}
+
+
+ );
+ }
+}
+
+export default HiddenMessage;
diff --git a/src/main-block/messages-block/hidden-message/index.ts b/src/main-block/messages-block/hidden-message/index.ts
new file mode 100644
index 0000000..9536208
--- /dev/null
+++ b/src/main-block/messages-block/hidden-message/index.ts
@@ -0,0 +1,2 @@
+import HiddenMessage from './hidden-message'
+export default HiddenMessage
diff --git a/src/main-block/messages-block/index.ts b/src/main-block/messages-block/index.ts
new file mode 100644
index 0000000..54f0a44
--- /dev/null
+++ b/src/main-block/messages-block/index.ts
@@ -0,0 +1,2 @@
+import MessagesBlock from './messages-block'
+export default MessagesBlock
diff --git a/src/main-block/messages-block/message/index.ts b/src/main-block/messages-block/message/index.ts
new file mode 100644
index 0000000..3a718b6
--- /dev/null
+++ b/src/main-block/messages-block/message/index.ts
@@ -0,0 +1,2 @@
+import Message from './message'
+export default Message
diff --git a/src/main-block/messages-block/message/message.module.css b/src/main-block/messages-block/message/message.module.css
new file mode 100644
index 0000000..35758c9
--- /dev/null
+++ b/src/main-block/messages-block/message/message.module.css
@@ -0,0 +1,124 @@
+.select-message__checkbox {
+ margin-top: 12px;
+ margin-right: 10px;
+ margin-left: 20px;
+}
+
+.select-message__checkbox-label {
+ display: inline-block;
+ width: 46px;
+ height: 40px;
+ cursor: pointer;
+ vertical-align: middle;
+}
+
+.message-container {
+ width: calc(100% - 46px - 3px);
+ padding: 0;
+ padding-right: 3px;
+
+ border: 0;
+ cursor: pointer;
+ font-family: inherit;
+ font-size: 100%;
+ outline: none;
+}
+
+.message-container:focus {
+ box-shadow: 0 0 0 2px #d9f7ff;
+}
+
+.message {
+ overflow: hidden;
+ height: 40px;
+
+ border-bottom: solid 0 #e2e2e2;
+ font-family: 'Helvetica Neue', sans-serif;
+ font-size: 13px;
+ opacity: 0;
+ text-overflow: ellipsis;
+ transition: opacity 1.5s, height 1.5s, border-bottom-width 1.5s;
+ white-space: nowrap;
+ will-change: transform, height, opacity;
+}
+
+.message-info__sender-logo {
+ display: inline-block;
+ width: 30px;
+ height: 30px;
+ background-color: #f33;
+
+ border-radius: 100px;
+ color: #fff;
+ line-height: 30px;
+ text-align: center;
+ vertical-align: middle;
+}
+
+.bold {
+ font-weight: bold;
+}
+
+.message-info__sender {
+ display: inline-block;
+ overflow: hidden;
+ width: 175px;
+ margin-left: 10px;
+ color: #000;
+ line-height: 30px;
+ text-align: left;
+ text-overflow: ellipsis;
+ vertical-align: middle;
+
+ white-space: nowrap;
+}
+
+.message-info__mark {
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ margin-right: 10px;
+ margin-left: 10px;
+ vertical-align: middle;
+}
+
+.unread-mark {
+ background-color: #6287bd;
+ border-radius: 100px;
+}
+
+.message-info__subject {
+ display: inline-block;
+ overflow: hidden;
+ width: calc(100% - 325px);
+ color: #000;
+ text-align: left;
+ text-overflow: ellipsis;
+ vertical-align: middle;
+ white-space: nowrap;
+}
+
+.message-info__date-container {
+ display: inline-block;
+ overflow: hidden;
+ width: 70px;
+ margin-right: 10px;
+ margin-left: 0;
+ color: #9b9b9b;
+ text-overflow: ellipsis;
+ vertical-align: middle;
+ white-space: nowrap;
+}
+
+.date-container__date {
+ float: right;
+}
+
+.unshrink {
+ border-bottom: solid 1px #e2e2e2;
+ opacity: 1;
+}
+
+.shrink {
+ opacity: 0;
+}
diff --git a/src/main-block/messages-block/message/message.tsx b/src/main-block/messages-block/message/message.tsx
new file mode 100644
index 0000000..f0c0ca8
--- /dev/null
+++ b/src/main-block/messages-block/message/message.tsx
@@ -0,0 +1,64 @@
+import React from 'react';
+
+import styles from './message.module.css';
+import checkboxStyle from '../messages-block.module.css'
+import { MessageInterface } from '../../../app';
+
+interface InjectedProps {
+ message: MessageInterface;
+ openMessage: (message: MessageInterface) => void;
+ selectCheckbox: (messageIndex: number) => void;
+ messageIndex: number;
+}
+
+class Message extends React.Component {
+ render() {
+ const classAddition =
+ (this.props.message.unshrink ? 'unshrink' : '') +
+ (this.props.message.shrink ? 'shrink' : '');
+ return (
+
+
+
+
+
+ );
+ }
+}
+
+export default Message;
diff --git a/src/main-block/messages-block/messages-block.module.css b/src/main-block/messages-block/messages-block.module.css
new file mode 100644
index 0000000..270c35d
--- /dev/null
+++ b/src/main-block/messages-block/messages-block.module.css
@@ -0,0 +1,49 @@
+.messages-block {
+ display: inline-block;
+ width: 78%;
+ min-width: 600px;
+ height: 100%;
+ margin-right: 0;
+ margin-left: 2%;
+ background-color: #fff;
+ border-radius: 4px;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
+
+ vertical-align: top;
+}
+
+.messages-list {
+ overflow: auto;
+ min-height: 500px;
+ max-height: 1231px;
+
+ border-bottom: solid 1px #e2e2e2;
+}
+
+.messages-list__open {
+ display: block;
+}
+
+.messages-list__closed {
+ display: none;
+}
+
+.checkbox {
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ background-color: #fff;
+
+ border-radius: 3px;
+ cursor: pointer;
+}
+
+.virtualizedList {
+ outline: none;
+}
+
+.virtualizedList:focus {
+ outline: 2px solid #d9f7ff;
+}
diff --git a/src/main-block/messages-block/messages-block.tsx b/src/main-block/messages-block/messages-block.tsx
new file mode 100644
index 0000000..f9a39a0
--- /dev/null
+++ b/src/main-block/messages-block/messages-block.tsx
@@ -0,0 +1,138 @@
+import React from 'react';
+
+import styles from './messages-block.module.css';
+import Header from './header';
+import HiddenMessage from './hidden-message';
+import Message from './message';
+import Footer from './footer';
+import { MessageInterface } from '../../app';
+import { AutoSizer, List } from 'react-virtualized';
+
+interface InjectedProps {
+ messagesList: MessageInterface[];
+ messagesPerPage: number;
+ updateList: (newList: MessageInterface[]) => void;
+}
+
+interface State {
+ hiddenMessageText: String,
+ messageIsOpen: boolean,
+ selectAllCheckbox: boolean,
+ messagesList: MessageInterface[]
+}
+
+class MessagesBlock extends React.Component {
+ public state: State;
+
+ constructor(props: InjectedProps) {
+ super(props);
+ this.openMessage = this.openMessage.bind(this);
+ this.closeMessage = this.closeMessage.bind(this);
+
+ this.handleSelectAll = this.handleSelectAll.bind(this);
+ this.selectCheckbox = this.selectCheckbox.bind(this);
+
+ this.deleteSelectedMessages = this.deleteSelectedMessages.bind(this);
+
+ this.state = {
+ hiddenMessageText: '',
+ messageIsOpen: false,
+ selectAllCheckbox: false,
+ messagesList: this.props.messagesList
+ };
+ }
+
+ closeMessage() {
+ this.setState({
+ messageIsOpen: false
+ });
+ }
+
+ openMessage(message: MessageInterface) {
+ this.setState({
+ messageIsOpen: true,
+ hiddenMessageText: message.hiddenText
+ });
+ }
+
+ handleSelectAll() {
+ this.setState((prevState: State) => {
+ const newMessagesList = prevState.messagesList;
+ for (let i = 0; i < newMessagesList.length; i++) {
+ newMessagesList[i].selected = !prevState.selectAllCheckbox;
+ }
+
+ return {
+ selectAllCheckbox: !prevState.selectAllCheckbox,
+ messagesList: newMessagesList
+ };
+ });
+ }
+
+ selectCheckbox(messageIndex: number) {
+ this.setState((prevState: State) => {
+ const newMessagesList = prevState.messagesList;
+ newMessagesList[messageIndex].selected = !newMessagesList[messageIndex].selected;
+ return {
+ messagesList: newMessagesList
+ };
+ });
+ }
+
+ deleteSelectedMessages() {
+ this.setState((prevState: State) => {
+ const newMessagesList = prevState.messagesList.filter(message => !message.selected);
+ this.props.updateList(newMessagesList);
+ });
+ }
+
+ render() {
+ this.state.messagesList = this.props.messagesList;
+ this.state.selectAllCheckbox = this.state.messagesList.some(message => message.selected);
+
+ const messagesListClassAddition = !this.state.messageIsOpen ? '__open' : '__closed';
+ const messagesCount = this.props.messagesList.length < this.props.messagesPerPage ?
+ this.props.messagesList.length : this.props.messagesPerPage;
+ return (
+
+
+
+
+
+ {({ height, width }) => (
+ {
+ return
+
+
;
+ }
+ }
+ />
+ )}
+
+
+
+
+ );
+ }
+}
+
+export default MessagesBlock;
diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts
new file mode 100644
index 0000000..6431bc5
--- /dev/null
+++ b/src/react-app-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/src/resources/yandex-mail.png b/src/resources/yandex-mail.png
new file mode 100644
index 0000000..08b572a
Binary files /dev/null and b/src/resources/yandex-mail.png differ
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..0980b23
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "preserve"
+ },
+ "include": [
+ "src"
+ ]
+}