diff --git a/js-core/classworks/classwork-19/match.js b/js-core/classworks/classwork-19/match.js new file mode 100644 index 0000000..5511f8c --- /dev/null +++ b/js-core/classworks/classwork-19/match.js @@ -0,0 +1,8 @@ +const add = (a,b) => { + return a + b; +}; + + +module.exports = { + add +} \ No newline at end of file diff --git a/js-core/classworks/classwork-19/node.js b/js-core/classworks/classwork-19/node.js new file mode 100644 index 0000000..eab69ad --- /dev/null +++ b/js-core/classworks/classwork-19/node.js @@ -0,0 +1,7 @@ +const {add} = require('./match'); + +// const add = (a,b) => { +// return a + b +// }; + +console.log(add(1,2)); \ No newline at end of file diff --git a/js-core/classworks/classwork-19/server.js b/js-core/classworks/classwork-19/server.js new file mode 100644 index 0000000..8387010 --- /dev/null +++ b/js-core/classworks/classwork-19/server.js @@ -0,0 +1,15 @@ +const http = require('http'); +const fs = require('fs'); + +http + .createServer ((request, response) => { + const index = fs.readFileSync('./index.html'); + response.end(index); + if (fs.readFileSync(`.${request.url}`)) { + const file = fs.readFileSync(`.${request.url}`); + response.end(file); + } + }) + .listen(3000, err => { + console.log('server started http://localhost:3000') + }); \ No newline at end of file diff --git a/js-core/classworks/classwork-22/src/app.js b/js-core/classworks/classwork-22/src/app.js new file mode 100644 index 0000000..c5dbd01 --- /dev/null +++ b/js-core/classworks/classwork-22/src/app.js @@ -0,0 +1,14 @@ +(function() { + + const initialState = [ + 'learn JavaScript', + 'learn MVC', + 'reed book' + ]; + + const model = new Model(initialState); + const view = new View(initialState); + const controller = new Controller(model, view); + +})(); + diff --git a/js-core/classworks/classwork-22/src/controller.js b/js-core/classworks/classwork-22/src/controller.js new file mode 100644 index 0000000..c638118 --- /dev/null +++ b/js-core/classworks/classwork-22/src/controller.js @@ -0,0 +1 @@ +function Controller (view, model) {} \ No newline at end of file diff --git a/js-core/classworks/classwork-22/src/model.js b/js-core/classworks/classwork-22/src/model.js new file mode 100644 index 0000000..dcd49e8 --- /dev/null +++ b/js-core/classworks/classwork-22/src/model.js @@ -0,0 +1,6 @@ +class Model { + constructor (initialState) { + this.data = initialState; + } + +} \ No newline at end of file diff --git a/js-core/classworks/classwork-22/src/view.js b/js-core/classworks/classwork-22/src/view.js new file mode 100644 index 0000000..7fb6cd0 --- /dev/null +++ b/js-core/classworks/classwork-22/src/view.js @@ -0,0 +1,7 @@ +class View { + + constructor (initialState) { + this.data = initialState; + } + +} \ No newline at end of file diff --git a/phoneApp/css/main.css b/phoneApp/css/main.css new file mode 100644 index 0000000..f9d530c --- /dev/null +++ b/phoneApp/css/main.css @@ -0,0 +1,333 @@ +/** + * Description: main styles + * Version: 1.0.0 + * Last update: 09.01.2017 + * Author: alex.maslennikova19@gmail.com + */ +/*$breakpoints: ( + 'screen-xs': 480px, + 'screen-sm': 768px, + 'screen-md': 992px, + 'screen-lg': 1200px +); +// keywords +$media-expressions: ( + 'screen': 'screen', + 'print': 'print', + 'handheld': 'handheld', + 'landscape': '(orientation: landscape)', + 'portrait': '(orientation: portrait)', + 'retina2x': '(-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi)', + 'retina3x': '(-webkit-min-device-pixel-ratio: 2.5), (min-resolution: 240dpi)' +);*/ +.clearfix:after { + content: ''; + display: table; + clear: both; +} + +.ellipsis { + white-space: nowrap; + /* 1 */ + text-overflow: ellipsis; + /* 2 */ + overflow: hidden; +} + +a:hover, a:focus, a:active, a.active { + color: #777; + text-decoration: none; +} + +button { + outline-color: white; +} + +h2 { + margin: 0; +} + +h3 { + margin: 0; +} + +.container { + width: 400px; + background: #ccc; + padding: 25px; +} + +.top-radius { + border-top-left-radius: 30px; + border-top-right-radius: 30px; +} + +.bottom-radius { + border-bottom-left-radius: 30px; + border-bottom-right-radius: 30px; +} + +.header { + text-align: center; + margin-top: 20px; +} + +.main-nav { + display: flex; + justify-content: space-between; +} + +.keypad-holder { + width: 250px; + margin: 0 auto; + display: flex; + flex-wrap: wrap; + justify-content: space-around; +} + +.number { + margin: 0 auto; + width: 200px; + height: 50px; + margin-bottom: 20px; + border-bottom: 1px solid #eee; + display: flex; + justify-content: space-between; +} + +.number .numbers { + align-self: flex-end; +} + +.key { + width: 60px; + min-height: 60px; + padding: 15px 0; + border-radius: 50%; + border: 1px solid #fff; + text-align: center; + margin-bottom: 10px; + margin-left: 5px; + margin-right: 5px; + display: flex; + align-items: center; + justify-content: center; +} + +.key:last-child { + background: #4cda64; + color: #fff; +} + +.key:last-child:hover { + background: #348f3c; + border: 1px solid #fff; +} + +.key:hover { + background: rgba(0, 0, 0, 0.1); + border: 1px solid #777; +} + +.tab { + display: flex; + flex-direction: column; + align-items: center; +} + +.tab:hover, .tab:focus, .tab:active, .tab.active { + color: #777; + text-decoration: none; +} + +.tab-text { + font-size: 12px; + margin-top: 5px; +} + +.contacts tbody { + display: block; + max-height: 300px; + overflow: hidden; + overflow-y: auto; +} + +.contacts tr { + display: table; + width: 100%; + table-layout: fixed; +} + +.table > thead > tr > th { + border: none; + cursor: pointer; +} + +.form-inline .form-control { + margin-bottom: 10px; + width: 100%; +} + +.form-inline .form-group { + display: block; +} + +.user-top-line { + display: flex; + justify-content: space-between; + margin-bottom: 20px; + font-size: 14px; +} + +.user-img { + width: 100px; + height: 100px; + margin-bottom: 10px; +} + +.user-name { + font-weight: bold; + text-align: center; + margin-bottom: 30px; +} + +.options-line { + display: flex; + justify-content: space-between; + text-align: center; + margin-bottom: 20px; +} + +.options-icon { + border-radius: 50%; + background: #000; + color: #fff; + width: 40px; + height: 40px; + margin: 0 auto 5px; + position: relative; +} + +.options-icon:hover { + background: #777; +} + +.options-text { + font-size: 12px; +} + +.message, .call, .video, .mail { + display: flex; + flex-direction: column; +} + +.icon { + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); +} + +.tel-number { + margin-bottom: 20px; + font-size: 16px; +} + +.options-table { + margin-bottom: 20px; + font-size: 16px; +} + +.options-item { + padding: 10px 0; + border-bottom: 1px solid #777; +} + +.options-item:first-child { + border-top: 1px solid #777; +} + +.scroll-holder { + max-height: 300px; + overflow: hidden; + overflow-y: auto; + margin-bottom: 20px; +} + +.add-foto-btn { + width: 100px; + height: 100px; + border-radius: 50%; + border: 1px solid #eee; + outline: none; +} + +.add-btn { + border: 0; + background: transparent; + width: 100%; + text-align: left; + outline-color: gray; +} + +.done-btn { + background: transparent; + border: none; + outline: none; +} + +.done-btn:hover { + color: #777; +} + +.main-info-holder { + flex-grow: 1; +} + +.edit-main-info { + display: flex; + justify-content: space-between; + margin-bottom: 20px; +} + +.edit-foto { + margin-right: 10px; +} + +.edit-field { + padding: 5px 0; + border-bottom: 1px solid #fff; + width: 100%; + display: flex; +} + +.delete-btn { + color: #e32910; + margin-top: 3px; + margin-right: 10px; + border: none; + background: transparent; +} + +.delete-btn:hover, .delete-btn:focus, .delete-btn:active { + color: #a70b0f; +} + +.add-btn { + color: #4cda64; + margin-top: 3px; + margin-right: 10px; +} + +.add-btn:hover, .add-btn:focus, .add-btn:active { + color: #348f3c; +} + +.delete-contact { + color: #e32910; + margin: 0 auto; + font-weight: bold; +} + diff --git a/phoneApp/images/user-face-mini.png b/phoneApp/images/user-face-mini.png new file mode 100644 index 0000000..1996df7 Binary files /dev/null and b/phoneApp/images/user-face-mini.png differ diff --git a/phoneApp/images/user-face.png b/phoneApp/images/user-face.png new file mode 100644 index 0000000..92f94dd Binary files /dev/null and b/phoneApp/images/user-face.png differ diff --git a/phoneApp/index.html b/phoneApp/index.html new file mode 100644 index 0000000..a145a7f --- /dev/null +++ b/phoneApp/index.html @@ -0,0 +1,29 @@ + + + + + + + Contacts + + + + + + + + +
+ +
+ + + + + + + + + + + diff --git a/phoneApp/js/add-user.js b/phoneApp/js/add-user.js new file mode 100644 index 0000000..d070d7c --- /dev/null +++ b/phoneApp/js/add-user.js @@ -0,0 +1,153 @@ +class AddUser { + constructor (store) { + this.store = store; + } + + componentDidMount() { + api.getAllUsers().then(users => { + this.store.setState(users); + }); + } + + render() { + const app = document.querySelector('#app'); + app.innerHTML = this.renderHeader() +this.renderMain(); + this.addUser(); + } + renderHeader() { + return `
+
+ +
+
` + } + renderMain () { + return `
+
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
` + } + + /*Добавляем юзера на сервер*/ + + serverAddUser (user) { + const url = 'http://easycode-js.herokuapp.com/olku/users'; + fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(user) + }); + this.componentDidMount(); + }; + + conversionTextContent(text) { + return text.replace(/\s/g, ""); + } + + /*Добавляем юзера при клике*/ + + addUser() { + const btnDone = document.querySelector('.done-btn'); + const user = {}; + + + btnDone.addEventListener('click', () => { + const fields = document.querySelectorAll('.edit-field'); + if (this.conversionTextContent(fields[0].textContent) !== 'FirstName') { + user.fullName = this.conversionTextContent(fields[0].textContent) + } + if (this.conversionTextContent(fields[1].textContent)!== 'LastName') { + user.fullName += ' ' + this.conversionTextContent(fields[1].textContent) + } + if (this.conversionTextContent(fields[3].textContent) !== 'phone') { + user.phone = this.conversionTextContent(fields[3].textContent) + } + if (this.conversionTextContent(fields[5].textContent)!== 'email') { + user.email = this.conversionTextContent(fields[5].textContent) + } + this.serverAddUser(user); + this.activeLink(); + this.store.setState({activePage : 'contacts'}); + }); + + } + activeLink () { + const links = document.querySelectorAll('.tab'); + const contacts = document.querySelector('.contacts'); + links.forEach(link => { + link.classList.remove('active') + }); + contacts.classList.add('active'); + } + +} diff --git a/phoneApp/js/app.js b/phoneApp/js/app.js new file mode 100644 index 0000000..e92b19c --- /dev/null +++ b/phoneApp/js/app.js @@ -0,0 +1,70 @@ +class App { + constructor() { + const store = this.createStore(); + + this.pages = { + contacts: new ContactPage(store), + keypad: new KeypadPage(store), + editcontact: new EditUser(store), + user: new User(store), + adduser: new AddUser(store), + }; + + this.state = store.getState(); + + this.router = new Router(store); + + this.render(); + } + + + createStore() { + let state = { + users: [], + activePage: 'contacts', + activeId: '', + }; + + const getState = () => { + return state; + }; + + const setState = (newState) => { + state = { + ...state, + ...newState, + }; + this.pages[state.activePage].render(); + }; + + return { + getState, + setState + } + } + + updateView() { + const activePage = this.state.activePage; + this.pages[activePage].componentDidMount(); + } + + render() { + this.router.render(); + this.updateView(); + } + + + static initialize() { + const app = new App(); + app.render(); + } +} + + + + +App.initialize(); + + + + diff --git a/phoneApp/js/contacts.js b/phoneApp/js/contacts.js new file mode 100644 index 0000000..dc7415a --- /dev/null +++ b/phoneApp/js/contacts.js @@ -0,0 +1,199 @@ +class ContactPage { + constructor (store) { + this.store = store; + this.users = []; + } + + componentDidMount() { + api.getAllUsers().then(users => { + this.store.setState({users}); + }); + } + + render() { + const {users} = this.store.getState(); + this.users = this.constructorUsers(users); + const app = document.querySelector('#app'); + app.innerHTML = this.renderHeader() + this.renderMain(); + this.addEventHandlers(); + } + + constructorUsers(users) { + let newUsers = []; + users.map(user => { + let newUser = {}; + newUser.id = user._id; + newUser.name = user.fullName.split(' ')[0]; + newUser.lastName = user.fullName.split(' ')[1] ? user.fullName.split(' ')[1] : ' '; + newUser.email = user.email; + newUser.phone = user.phone; + newUsers.push(newUser); + }); + return newUsers; + } + + renderHeader () { + return `
+
+

Contacts

+
+
` + } + + renderMain () { + return `
+
+
+
+ + +
+
+ + + + + + + + ${this.renderTable()} +
NameLastNameEmail
+
+
` + } + + renderTable() { + return ` + ${this.users.map(user => {return` + + ${user.id} + ${user.name} + ${user.lastName} + ${user.email} + ` + }).join('')} + ` + } + + /* Сортировка по параметрам */ + + sortByParameter (param) { + this.users.sort(function (a, b) { + if (a[param] > b[param]) { + return 1 + } + if (a[param] < b[param]) { + return -1 + } + return 0 + }); + }; + + /* Сортировка по клику */ + + sortByParameterOnClick () { + const paramsForSort = document.querySelectorAll('th'); + let tBody = document.querySelector('tbody'); + + paramsForSort[0].onclick = () => { + this.sortByParameter('name'); + tBody.innerHTML = this.renderTable() + }; + paramsForSort[1].onclick = () => { + this.sortByParameter('lastName'); + tBody.innerHTML = this.renderTable() + }; + paramsForSort[2].onclick = () => { + this.sortByParameter('email'); + tBody.innerHTML = this.renderTable() + }; + } + + + + /*Поиск в приложении */ + + search() { + const search = document.querySelector('#search'); + let tBody = document.querySelector('tbody'); + let param = ''; + + search.onkeypress = (event) => { + if (event.which === 13) { + param = ''; + } + else { + param += String.fromCharCode(event.keyCode).toLowerCase(); + } + let filterUsers; + filterUsers = this.users.filter(user => { + return user.email.includes(param) || user.name.includes(param) || user.lastName.includes(param); + }); + tBody.innerHTML = ` + ${filterUsers.map(user => {return` + + ${user.name} + ${user.lastName} + ${user.email} + ` + }).join('')} + ` + }; + search.onkeydown = (event) => { + if (event.which === 8 && param.length !== 0) { + param = param.slice(0, -1); + } + let filterUsers; + filterUsers = this.users.filter(user => { + return user.email.includes(param) || user.name.includes(param) || user.lastName.includes(param); + }); + tBody.innerHTML = ` + ${filterUsers.map(user => {return` + + ${user.name} + ${user.lastName} + ${user.email} + ` + }).join('')} + ` + } + } + + /*Пепеход по клику*/ + + + + clickThrough() { + const links = document.querySelectorAll('.tab'); + const user = document.querySelector('.user'); + let table = document.querySelector('tbody'); + table.addEventListener('click', event => { + let target = event.target; + let id = target.parentNode.childNodes[1].textContent; + links.forEach(link => { + link.classList.remove('active') + }); + user.classList.add('active'); + this.store.setState({activePage : 'user', activeId: id}); + }); + } + + + /*Подключаем все события*/ + + + addEventHandlers () { + this.search(); + this.sortByParameterOnClick(); + this.clickThrough(); + + + + } +} + + + + + + diff --git a/phoneApp/js/edit-user.js b/phoneApp/js/edit-user.js new file mode 100644 index 0000000..b824d2b --- /dev/null +++ b/phoneApp/js/edit-user.js @@ -0,0 +1,171 @@ +class EditUser { + constructor (store) { + this.store = store; + this.user = {}; + } + + componentDidMount() { + api.getAllUsers().then(users => { + this.store.setState(users); + }); + } + + constructorUser(user) { + let newUser = {}; + newUser.id = user._id; + newUser.name = user.fullName.split(' ')[0]; + newUser.lastName = user.fullName.split(' ')[1] ? user.fullName.split(' ')[1] : ' '; + newUser.email = user.email; + newUser.phone = user.phone; + return newUser; + } + + render() { + let store = this.store.getState(); + store.users.forEach(user => { + if (user._id === store.activeId) { + this.user = this.constructorUser(user); + } + }); + const app = document.querySelector('#app'); + app.innerHTML = this.renderHeader() +this.renderMain(); + this.addEventHandlers(); + } + + renderHeader() { + return `
+
+ +
+
` + } + + renderMain () { + return `
+
+
+
#
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
` + } + + addEventHandlers() { + this.editUser(); + this.deleteUser(); + this.canselEdit(); + } + + /*Изменяем юзера на сервер*/ + + serverEditUser (user, id) { + const url = "http://easycode-js.herokuapp.com/olku/users/" + id; + fetch(url, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(user) + }); + }; + + conversionTextContent(text) { + return text.replace(/\s/g, ""); + } + + /*Изменяем юзера при клике*/ + + editUser() { + const btnDone = document.querySelector('.done-btn'); + const {activeId} = this.store.getState(); + const user = {}; + + + btnDone.addEventListener('click', () => { + const fields = document.querySelectorAll('.edit-field'); + if (this.conversionTextContent(fields[0].textContent) !== 'FirstName') { + user.fullName = this.conversionTextContent(fields[0].textContent) + } + if (this.conversionTextContent(fields[1].textContent)!== 'LastName') { + user.fullName += ' ' + this.conversionTextContent(fields[1].textContent) + } + if (this.conversionTextContent(fields[3].textContent) !== 'add phone') { + user.phone = this.conversionTextContent(fields[3].textContent) + } + if (this.conversionTextContent(fields[5].textContent)!== 'add email') { + user.email = this.conversionTextContent(fields[5].textContent) + } + this.serverEditUser(user, activeId); + this.returnContact(); + }); + } + + serverDeleteUser(id) { + const url = "http://easycode-js.herokuapp.com/olku/users/" + id; + fetch(url, { + method: 'DELETE', + }); + this.componentDidMount(); + } + + deleteUser() { + const btnDelete = document.querySelector('.delete-contact'); + const {activeId} = this.store.getState(); + btnDelete.addEventListener('click', () => { + this.serverDeleteUser(activeId); + this.componentDidMount(); + this.returnContact(); + }) + } + + canselEdit() { + const btnCansel = document.querySelector('.cansel-btn'); + btnCansel.addEventListener('click', (event) => { + event.preventDefault(); + this.returnContact(); + }) + } + + returnContact () { + const links = document.querySelectorAll('.tab'); + const contacts = document.querySelector('.contacts'); + links.forEach(link => { + link.classList.remove('active') + }); + contacts.classList.add('active'); + this.store.setState({activePage : 'contacts'}); + } +} + diff --git a/phoneApp/js/get-usersAPI.js b/phoneApp/js/get-usersAPI.js new file mode 100644 index 0000000..33390df --- /dev/null +++ b/phoneApp/js/get-usersAPI.js @@ -0,0 +1,10 @@ +class Api { + getAllUsers() { + const url = 'http://easycode-js.herokuapp.com/olku/users'; + return fetch(url) + .then(users => { + return users.json() + }) + } +} +const api = new Api(); diff --git a/phoneApp/js/keypad.js b/phoneApp/js/keypad.js new file mode 100644 index 0000000..dcf8b3f --- /dev/null +++ b/phoneApp/js/keypad.js @@ -0,0 +1,127 @@ +class KeypadPage { + constructor(store) { + this.store = store; + } + componentDidMount() { + api.getAllUsers().then(users => { + this.store.setState(users); + }); + } + render() { + const app = document.querySelector('#app'); + app.innerHTML = this.renderHeader() +this.renderMain(); + this.dialingNumber(); + } + renderHeader() { + return `
+
+

Keypad

+
+
` + } + renderMain() { + return `
+
+
+ + + +
+
+ + + + + + + + + + + + + +
+
+
` + } + + /*Приведение к единому фомату номера*/ + + conversionPhoneFormat(phoneNumber) { + switch(phoneNumber.length) { + case 1: return phoneNumber.replace(/([0-9]{1})/, '($1)'); + case 2: return phoneNumber.replace(/([0-9]{2})/, '($1)'); + case 3: return phoneNumber.replace(/([0-9]{3})/, '($1)'); + case 4: return phoneNumber.replace(/([0-9]{3})([0-9]{1})/, '($1) $2'); break; + case 5: return phoneNumber.replace(/([0-9]{3})([0-9]{2})/, '($1) $2'); break; + case 6: return phoneNumber.replace(/([0-9]{3})([0-9]{2})([0-9]{1})/, '($1) $2-$3'); break; + case 7: return phoneNumber.replace(/([0-9]{3})([0-9]{2})([0-9]{2})/, '($1) $2-$3'); break; + case 8: return phoneNumber.replace(/([0-9]{3})([0-9]{2})([0-9]{2})([0-9]{1})/, '($1) $2-$3-$4'); break; + case 9: return phoneNumber.replace(/([0-9]{3})([0-9]{2})([0-9]{2})([0-9]{2})/, '($1) $2-$3-$4'); break; + case 10: return phoneNumber.replace(/([0-9]{3})([0-9]{2})([0-9]{2})([0-9]{3})/, '($1) $2-$3-$4'); break; + default: break; + } + } + + /*Удаляем пробелы и символы из номера*/ + + deleteSimbol(phoneNumber) { + return phoneNumber.replace(/\D/g, ""); + } + + /*Преобразовнаие символов при вводе с клавиатуры */ + + getChar(event) { + console.log(event.which); + if (event.which == null) { // IE + if (event.keyCode < 32) return null; // спец. символ + return String.fromCharCode(event.keyCode) + } + + if (event.which != 0 && event.charCode != 0) { // все кроме IE + if (event.which < 32) return null; // спец. символ + return String.fromCharCode(event.which); // остальные + } + + return null; // спец. символ + } + + /*Набор и удаление номера*/ + + dialingNumber() { + const entryField = document.querySelector('.numbers'); + + const buttons = document.querySelectorAll('button'); + + for (let i = 0; i < buttons.length; i++) { + buttons[i].onclick = () => { + if (this.deleteSimbol(entryField.textContent).length < 10) { + entryField.textContent = this.deleteSimbol(entryField.textContent) +`${buttons[i].textContent}`; + entryField.innerHTML = this.conversionPhoneFormat(entryField.textContent); + } + } + } + + /*Удалить цифру */ + + const buttonDelete = document.querySelector('.glyphicon-circle-arrow-left'); + + buttonDelete.onclick = () => { + entryField.innerHTML = `${entryField.textContent.slice(0, -1)}`; + }; + + /*Набор номера с клавиатуры */ + + document.onkeypress = (event) => { + if ( this.deleteSimbol(entryField.textContent).length < 10) { + entryField.textContent = this.deleteSimbol(entryField.textContent) + this.getChar(event); + entryField.innerHTML = this.conversionPhoneFormat(entryField.textContent); + } + }; + + } + +} + + diff --git a/phoneApp/js/router.js b/phoneApp/js/router.js new file mode 100644 index 0000000..f86c150 --- /dev/null +++ b/phoneApp/js/router.js @@ -0,0 +1,59 @@ +class Router { + /*Ренедеим футер*/ + constructor (store) { + this.store = store; + } + + render () { + const mountNode = document.querySelector('#mountNode'); + mountNode.innerHTML = ` +
+ + ` + this.switchRouter(); + } + /*Ренедеим ссылки в футере*/ + + renderLink (linkProperties) { + return ` + + + ${linkProperties.content} + ` + + } + + /*Преобразуем контент в сыылке в нужный формат */ + + conversionActiveLink(text) { + return text.replace(/-/g, "").replace(/.html/, ""); + } + + /*Переключатель вкладок в футере*/ + + switchRouter() { + const links = document.querySelectorAll('.tab'); + for (let i = 0; i < links.length; i++) { + let href = this.conversionActiveLink(links[i].getAttribute('href')); + links[i].addEventListener('click', (event) => { + event.preventDefault(); + this.store.setState({activePage : href}); + for (let j = 0; j < links.length; j ++) { + if (i !== j) { + links[j].classList.remove('active') + } + } + }) + } + } +} \ No newline at end of file diff --git a/phoneApp/js/user.js b/phoneApp/js/user.js new file mode 100644 index 0000000..b2bc0c5 --- /dev/null +++ b/phoneApp/js/user.js @@ -0,0 +1,97 @@ +class User { + constructor (store) { + this.store = store; + this.user = {}; + } + + componentDidMount() { + api.getAllUsers().then(users => { + this.store.setState(users); + }); + } + + render () { + let store = this.store.getState(); + store.users.forEach(user => { + if (user._id === store.activeId) { + this.user = user; + } + }); + const app = document.querySelector('#app'); + app.innerHTML = this.renderHeader() +this.renderMain(); + this.editUser(); + + } + + renderHeader () { + return`
+
+ +
+
` + + } + renderMain () { + return ` +
+
+ # +
${this.user.fullName}
+
+
+
+ message +
+
+
+ call +
+
+
+ video +
+
+
+ mail +
+
+
+

mobile

+
${this.user.phone}
+
+
+

email

+
${this.user.email}
+
+ +
+
+ ` + } + editUser () { + const editContact = document.querySelector('#edit-contact-top'); + const links = document.querySelectorAll('.tab'); + const editUser = document.querySelector('.edituser'); + editContact.addEventListener('click', (event) => { + event.preventDefault(); + links.forEach(link => { + link.classList.remove('active') + }); + editUser.classList.add('active'); + this.store.setState({activePage : 'editcontact'}); + }) + } + +} diff --git a/phoneApp/scss/base/_helpers.scss b/phoneApp/scss/base/_helpers.scss new file mode 100644 index 0000000..ca33d4c --- /dev/null +++ b/phoneApp/scss/base/_helpers.scss @@ -0,0 +1,59 @@ +// list reset +%listreset { + margin: 0; + padding: 0; + list-style: none; +} + +// clearfix +%clearfix { + &:after { + content: ''; + display: table; + clear: both; + } +} + +.clearfix { + @extend %clearfix; +} + + +// transition +%transition { + transition: all 0.3s ease-in-out; +} + +// justify nav +%justify { + text-align: justify; + font-size: 1px; + line-height: 0px; + > * { + display: inline-block; + vertical-align: top; + text-align: left; + font-size: $font-size-base; + line-height: $line-height-base; + } + &:after { + content: ''; + width: 100%; + display: inline-block; + vertical-align: top; + } +} + + + +/// * Helper class to truncate and add ellipsis to a string too long for it to fit +/// * on a single line. +/// * 1. Prevent content from wrapping, forcing it on a single line. +/// * 2. Add ellipsis at the end of the line. + +.ellipsis { + white-space: nowrap; /* 1 */ + text-overflow: ellipsis; /* 2 */ + overflow: hidden; +} + diff --git a/phoneApp/scss/base/_include-media.scss b/phoneApp/scss/base/_include-media.scss new file mode 100644 index 0000000..0815871 --- /dev/null +++ b/phoneApp/scss/base/_include-media.scss @@ -0,0 +1,560 @@ +@charset 'UTF-8'; + +// _ _ _ _ _ +// (_) | | | | | (_) +// _ _ __ ___| |_ _ __| | ___ _ __ ___ ___ __| |_ __ _ +// | | '_ \ / __| | | | |/ _` |/ _ \ | '_ ` _ \ / _ \/ _` | |/ _` | +// | | | | | (__| | |_| | (_| | __/ | | | | | | __/ (_| | | (_| | +// |_|_| |_|\___|_|\__,_|\__,_|\___| |_| |_| |_|\___|\__,_|_|\__,_| +// +// Simple, elegant and maintainable media queries in Sass +// v1.4.1 +// +// http://include-media.com +// +// Authors: Eduardo Boucas (@eduardoboucas) +// Hugo Giraudel (@hugogiraudel) +// +// This project is licensed under the terms of the MIT license + + +//// +/// include-media library public configuration +/// @author Eduardo Boucas +/// @access public +//// + + +/// +/// Creates a list of global breakpoints +/// +/// @example scss - Creates a single breakpoint with the label `phone` +/// $breakpoints: ('phone': 320px); +/// +$breakpoints: ( + 'screen-xs': 320px, + 'screen-md': 768px, + 'screen-lg': 1024px +) !default; + + +/// +/// Creates a list of static expressions or media types +/// +/// @example scss - Creates a single media type (screen) +/// $media-expressions: ('screen': 'screen'); +/// +/// @example scss - Creates a static expression with logical disjunction (OR operator) +/// $media-expressions: ( +/// 'retina2x': '(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)' +/// ); +/// +$media-expressions: ( + 'screen': 'screen', + 'print': 'print', + 'handheld': 'handheld', + 'landscape': '(orientation: landscape)', + 'portrait': '(orientation: portrait)', + 'retina2x': '(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)', + 'retina3x': '(-webkit-min-device-pixel-ratio: 3), (min-resolution: 350dpi)' +) !default; + + +/// +/// Defines a number to be added or subtracted from each unit when declaring breakpoints with exclusive intervals +/// +/// @example scss - Interval for pixels is defined as `1` by default +/// @include media('>128px') {} +/// +/// /* Generates: */ +/// @media (min-width: 129px) {} +/// +/// @example scss - Interval for ems is defined as `0.01` by default +/// @include media('>20em') {} +/// +/// /* Generates: */ +/// @media (min-width: 20.01em) {} +/// +/// @example scss - Interval for rems is defined as `0.1` by default, to be used with `font-size: 62.5%;` +/// @include media('>2.0rem') {} +/// +/// /* Generates: */ +/// @media (min-width: 2.1rem) {} +/// +$unit-intervals: ( + 'px': 1, + 'em': 0.01, + 'rem': 0.1 +) !default; + +/// +/// Defines whether support for media queries is available, useful for creating separate stylesheets +/// for browsers that don't support media queries. +/// +/// @example scss - Disables support for media queries +/// $im-media-support: false; +/// @include media('>=tablet') { +/// .foo { +/// color: tomato; +/// } +/// } +/// +/// /* Generates: */ +/// .foo { +/// color: tomato; +/// } +/// +$im-media-support: true !default; + +/// +/// Selects which breakpoint to emulate when support for media queries is disabled. Media queries that start at or +/// intercept the breakpoint will be displayed, any others will be ignored. +/// +/// @example scss - This media query will show because it intercepts the static breakpoint +/// $im-media-support: false; +/// $im-no-media-breakpoint: 'desktop'; +/// @include media('>=tablet') { +/// .foo { +/// color: tomato; +/// } +/// } +/// +/// /* Generates: */ +/// .foo { +/// color: tomato; +/// } +/// +/// @example scss - This media query will NOT show because it does not intercept the desktop breakpoint +/// $im-media-support: false; +/// $im-no-media-breakpoint: 'tablet'; +/// @include media('>=desktop') { +/// .foo { +/// color: tomato; +/// } +/// } +/// +/// /* No output */ +/// +$im-no-media-breakpoint: 'desktop' !default; + +/// +/// Selects which media expressions are allowed in an expression for it to be used when media queries +/// are not supported. +/// +/// @example scss - This media query will show because it intercepts the static breakpoint and contains only accepted media expressions +/// $im-media-support: false; +/// $im-no-media-breakpoint: 'desktop'; +/// $im-no-media-expressions: ('screen'); +/// @include media('>=tablet', 'screen') { +/// .foo { +/// color: tomato; +/// } +/// } +/// +/// /* Generates: */ +/// .foo { +/// color: tomato; +/// } +/// +/// @example scss - This media query will NOT show because it intercepts the static breakpoint but contains a media expression that is not accepted +/// $im-media-support: false; +/// $im-no-media-breakpoint: 'desktop'; +/// $im-no-media-expressions: ('screen'); +/// @include media('>=tablet', 'retina2x') { +/// .foo { +/// color: tomato; +/// } +/// } +/// +/// /* No output */ +/// +$im-no-media-expressions: ('screen', 'portrait', 'landscape') !default; + +//// +/// Cross-engine logging engine +/// @author Hugo Giraudel +/// @access private +//// + + +/// +/// Log a message either with `@error` if supported +/// else with `@warn`, using `feature-exists('at-error')` +/// to detect support. +/// +/// @param {String} $message - Message to log +/// +@function log($message) { + @if feature-exists('at-error') { + @error $message; + } @else { + @warn $message; + $_: noop(); + } + + @return $message; +} + + +/// +/// Wrapper mixin for the log function so it can be used with a more friendly +/// API than `@if log('..') {}` or `$_: log('..')`. Basically, use the function +/// within functions because it is not possible to include a mixin in a function +/// and use the mixin everywhere else because it's much more elegant. +/// +/// @param {String} $message - Message to log +/// +@mixin log($message) { + @if log($message) {} +} + + +/// +/// Function with no `@return` called next to `@warn` in Sass 3.3 +/// to trigger a compiling error and stop the process. +/// +@function noop() {} + +/// +/// Determines whether a list of conditions is intercepted by the static breakpoint. +/// +/// @param {Arglist} $conditions - Media query conditions +/// +/// @return {Boolean} - Returns true if the conditions are intercepted by the static breakpoint +/// +@function im-intercepts-static-breakpoint($conditions...) { + $no-media-breakpoint-value: map-get($breakpoints, $im-no-media-breakpoint); + + @if not $no-media-breakpoint-value { + @if log('`#{$im-no-media-breakpoint}` is not a valid breakpoint.') {} + } + + @each $condition in $conditions { + @if not map-has-key($media-expressions, $condition) { + $operator: get-expression-operator($condition); + $prefix: get-expression-prefix($operator); + $value: get-expression-value($condition, $operator); + + @if ($prefix == 'max' and $value <= $no-media-breakpoint-value) or + ($prefix == 'min' and $value > $no-media-breakpoint-value) { + @return false; + } + } @else if not index($im-no-media-expressions, $condition) { + @return false; + } + } + + @return true; +} + +//// +/// Parsing engine +/// @author Hugo Giraudel +/// @access private +//// + + +/// +/// Get operator of an expression +/// +/// @param {String} $expression - Expression to extract operator from +/// +/// @return {String} - Any of `>=`, `>`, `<=`, `<`, `≥`, `≤` +/// +@function get-expression-operator($expression) { + @each $operator in ('>=', '>', '<=', '<', '≥', '≤') { + @if str-index($expression, $operator) { + @return $operator; + } + } + + // It is not possible to include a mixin inside a function, so we have to + // rely on the `log(..)` function rather than the `log(..)` mixin. Because + // functions cannot be called anywhere in Sass, we need to hack the call in + // a dummy variable, such as `$_`. If anybody ever raise a scoping issue with + // Sass 3.3, change this line in `@if log(..) {}` instead. + $_: log('No operator found in `#{$expression}`.'); +} + + +/// +/// Get dimension of an expression, based on a found operator +/// +/// @param {String} $expression - Expression to extract dimension from +/// @param {String} $operator - Operator from `$expression` +/// +/// @return {String} - `width` or `height` (or potentially anything else) +/// +@function get-expression-dimension($expression, $operator) { + $operator-index: str-index($expression, $operator); + $parsed-dimension: str-slice($expression, 0, $operator-index - 1); + $dimension: 'width'; + + @if str-length($parsed-dimension) > 0 { + $dimension: $parsed-dimension; + } + + @return $dimension; +} + + +/// +/// Get dimension prefix based on an operator +/// +/// @param {String} $operator - Operator +/// +/// @return {String} - `min` or `max` +/// +@function get-expression-prefix($operator) { + @return if(index(('<', '<=', '≤'), $operator), 'max', 'min'); +} + + +/// +/// Get value of an expression, based on a found operator +/// +/// @param {String} $expression - Expression to extract value from +/// @param {String} $operator - Operator from `$expression` +/// +/// @return {Number} - A numeric value +/// +@function get-expression-value($expression, $operator) { + $operator-index: str-index($expression, $operator); + $value: str-slice($expression, $operator-index + str-length($operator)); + + @if map-has-key($breakpoints, $value) { + $value: map-get($breakpoints, $value); + } @else { + $value: to-number($value); + } + + $interval: map-get($unit-intervals, unit($value)); + + @if not $interval { + // It is not possible to include a mixin inside a function, so we have to + // rely on the `log(..)` function rather than the `log(..)` mixin. Because + // functions cannot be called anywhere in Sass, we need to hack the call in + // a dummy variable, such as `$_`. If anybody ever raise a scoping issue with + // Sass 3.3, change this line in `@if log(..) {}` instead. + $_: log('Unknown unit `#{unit($value)}`.'); + } + + @if $operator == '>' { + $value: $value + $interval; + } @else if $operator == '<' { + $value: $value - $interval; + } + + @return $value; +} + + +/// +/// Parse an expression to return a valid media-query expression +/// +/// @param {String} $expression - Expression to parse +/// +/// @return {String} - Valid media query +/// +@function parse-expression($expression) { + // If it is part of $media-expressions, it has no operator + // then there is no need to go any further, just return the value + @if map-has-key($media-expressions, $expression) { + @return map-get($media-expressions, $expression); + } + + $operator: get-expression-operator($expression); + $dimension: get-expression-dimension($expression, $operator); + $prefix: get-expression-prefix($operator); + $value: get-expression-value($expression, $operator); + + @return '(#{$prefix}-#{$dimension}: #{$value})'; +} + +/// +/// Slice `$list` between `$start` and `$end` indexes +/// +/// @access private +/// +/// @param {List} $list - List to slice +/// @param {Number} $start [1] - Start index +/// @param {Number} $end [length($list)] - End index +/// +/// @return {List} Sliced list +/// +@function slice($list, $start: 1, $end: length($list)) { + @if length($list) < 1 or $start > $end { + @return (); + } + + $result: (); + + @for $i from $start through $end { + $result: append($result, nth($list, $i)); + } + + @return $result; +} + +//// +/// String to number converter +/// @author Hugo Giraudel +/// @access private +//// + + +/// +/// Casts a string into a number +/// +/// @param {String | Number} $value - Value to be parsed +/// +/// @return {Number} +/// +@function to-number($value) { + @if type-of($value) == 'number' { + @return $value; + } @else if type-of($value) != 'string' { + $_: log('Value for `to-number` should be a number or a string.'); + } + + $result: 0; + $digits: 0; + $minus: str-slice($value, 1, 1) == '-'; + $numbers: ('0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9); + + @for $i from if($minus, 2, 1) through str-length($value) { + $character: str-slice($value, $i, $i); + + @if not (index(map-keys($numbers), $character) or $character == '.') { + @return to-length(if($minus, -$result, $result), str-slice($value, $i)) + } + + @if $character == '.' { + $digits: 1; + } @else if $digits == 0 { + $result: $result * 10 + map-get($numbers, $character); + } @else { + $digits: $digits * 10; + $result: $result + map-get($numbers, $character) / $digits; + } + } + + @return if($minus, -$result, $result);; +} + + +/// +/// Add `$unit` to `$value` +/// +/// @param {Number} $value - Value to add unit to +/// @param {String} $unit - String representation of the unit +/// +/// @return {Number} - `$value` expressed in `$unit` +/// +@function to-length($value, $unit) { + $units: ('px': 1px, 'cm': 1cm, 'mm': 1mm, '%': 1%, 'ch': 1ch, 'pc': 1pc, 'in': 1in, 'em': 1em, 'rem': 1rem, 'pt': 1pt, 'ex': 1ex, 'vw': 1vw, 'vh': 1vh, 'vmin': 1vmin, 'vmax': 1vmax); + + @if not index(map-keys($units), $unit) { + $_: log('Invalid unit `#{$unit}`.'); + } + + @return $value * map-get($units, $unit); +} + +/// +/// This mixin aims at redefining the configuration just for the scope of +/// the call. It is helpful when having a component needing an extended +/// configuration such as custom breakpoints (referred to as tweakpoints) +/// for instance. +/// +/// @author Hugo Giraudel +/// +/// @param {Map} $tweakpoints [()] - Map of tweakpoints to be merged with `$breakpoints` +/// @param {Map} $tweak-media-expressions [()] - Map of tweaked media expressions to be merged with `$media-expression` +/// +/// @example scss - Extend the global breakpoints with a tweakpoint +/// @include media-context(('custom': 678px)) { +/// .foo { +/// @include media('>phone', '<=custom') { +/// // ... +/// } +/// } +/// } +/// +/// @example scss - Extend the global media expressions with a custom one +/// @include media-context($tweak-media-expressions: ('all': 'all')) { +/// .foo { +/// @include media('all', '>phone') { +/// // ... +/// } +/// } +/// } +/// +/// @example scss - Extend both configuration maps +/// @include media-context(('custom': 678px), ('all': 'all')) { +/// .foo { +/// @include media('all', '>phone', '<=custom') { +/// // ... +/// } +/// } +/// } +/// +@mixin media-context($tweakpoints: (), $tweak-media-expressions: ()) { + // Save global configuration + $global-breakpoints: $breakpoints; + $global-media-expressions: $media-expressions; + + // Update global configuration + $breakpoints: map-merge($breakpoints, $tweakpoints) !global; + $media-expressions: map-merge($media-expressions, $tweak-media-expressions) !global; + + @content; + + // Restore global configuration + $breakpoints: $global-breakpoints !global; + $media-expressions: $tweak-media-expressions !global; +} + +//// +/// include-media public exposed API +/// @author Eduardo Boucas +/// @access public +//// + + +/// +/// Generates a media query based on a list of conditions +/// +/// @param {Arglist} $conditions - Media query conditions +/// +/// @example scss - With a single set breakpoint +/// @include media('>phone') { } +/// +/// @example scss - With two set breakpoints +/// @include media('>phone', '<=tablet') { } +/// +/// @example scss - With custom values +/// @include media('>=358px', '<850px') { } +/// +/// @example scss - With set breakpoints with custom values +/// @include media('>desktop', '<=1350px') { } +/// +/// @example scss - With a static expression +/// @include media('retina2x') { } +/// +/// @example scss - Mixing everything +/// @include media('>=350px', ' 0) { + @media #{unquote(parse-expression(nth($conditions, 1)))} { + // Recursive call + @include media(slice($conditions, 2)...) { + @content; + } + } + } +} \ No newline at end of file diff --git a/phoneApp/scss/base/_mixins.scss b/phoneApp/scss/base/_mixins.scss new file mode 100644 index 0000000..ddaf74d --- /dev/null +++ b/phoneApp/scss/base/_mixins.scss @@ -0,0 +1,154 @@ +// vertical align el inside parent with fixed height/min-height +// usage +// html - .parent>.child +// scss - @include v-align; +// or @include v-align(250px); +// or @include v-align(250px, bottom, before); +// +@mixin v-align($va-height: 100%, $va-direction: middle, $va-pseudo: after) { + white-space: nowrap; + text-align: center; + + &:#{$va-pseudo} { + content: ''; + display: inline-block; + vertical-align: $va-direction; + width: 0; + min-height: $va-height; + } + + > * { + white-space: normal; + display: inline-block; + vertical-align: $va-direction; + max-width: 99%; + } +} + +@mixin v-align-pair($child-name1, $child-name2, $valign: middle, $width1: auto, $width2: auto ) { + display: table; + .#{$child-name1}{ + display: table-cell; + vertical-align: $valign; + width: $width1; + } + .#{$child-name2} { + display: table-cell; + vertical-align: $valign; + width: $width2; + } +} + + +// vertical/horizontal align el +// usage: @include vertical-align-el; +// +@mixin xycenter { + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%,-50%); + -ms-transform: translate(-50%,-50%); + transform: translate(-50%,-50%); +} +@mixin xcenter { + position: absolute; + left: 50%; + -webkit-transform: translate(-50%,0); + -ms-transform: translate(-50%,0); + transform: translate(-50%,0); +} +@mixin ycenter { + position: absolute; + top: 50%; + -webkit-transform: translate(0,-50%); + -ms-transform: translate(0,-50%); + transform: translate(0,-50%); +} + +@mixin size($width, $height: $width) { + width: $width; + height: $height; +} + +/// font-smothing +/// @include font-smoothing(on); +/// @include font-smoothing(off); +@mixin font-smoothing($value:on){ + @if $value == on{ + -webkit-font-smoothing:antialiased; + -moz-osx-font-smoothing:grayscale; + } + @else{ + -webkit-font-smoothing:subpixel-antialiased; + -moz-osx-font-smoothing:auto; + } +} + +@mixin hide-text { + overflow: hidden; + text-indent: 101%; + white-space: nowrap; +} + +/// Animate css properties +// usage +// scss - @include animate(color); +// or @include animate(color width); +// or @include animate(color width, 1s, linear); +// +$animation-speed: .3s !default; + +@mixin animate($properties, $duration: $animation-speed, $easing: ease-in-out) { + $list:(); + @each $prop in $properties { + $str: #{$prop} #{$animation-speed} #{$easing}; + $list: join($list, $str, comma); + } + transition: $list; +} + +@mixin placeholder { + &::-webkit-input-placeholder {@content} + &::-moz-placeholder {opacity: 1; @content} + &:-moz-placeholder {@content} + &:-ms-input-placeholder {@content} + &.placeholder {@content} +} +@mixin anim($param: all, $dur: .2s, $type: ease-in-out, $delay: 0s) { + transition: $param $dur $type $delay; +} +@mixin anim-param($params...) { + transition-property: $params; +} + +// ============================================================================= +// Font Face +// ============================================================================= + +@mixin font-face($name, $path, $weight: null, $style: null, $exts: eot woff2 woff ttf svg) { + $src: null; + + $extmods: ( + eot: "?", + svg: "#" + str-replace($name, " ", "_") + ); + + $formats: ( + otf: "opentype", + ttf: "truetype" + ); + + @each $ext in $exts { + $extmod: if(map-has-key($extmods, $ext), $ext + map-get($extmods, $ext), $ext); + $format: if(map-has-key($formats, $ext), map-get($formats, $ext), $ext); + $src: append($src, url(quote($path + "." + $extmod)) format(quote($format)), comma); + } + + @font-face { + font-family: quote($name); + font-style: $style; + font-weight: $weight; + src: $src; + } +} diff --git a/phoneApp/scss/main.scss b/phoneApp/scss/main.scss new file mode 100644 index 0000000..3f68419 --- /dev/null +++ b/phoneApp/scss/main.scss @@ -0,0 +1,332 @@ +/** + * Description: main styles + * Version: 1.0.0 + * Last update: 09.01.2017 + * Author: alex.maslennikova19@gmail.com + */ + +/*$breakpoints: ( + 'screen-xs': 480px, + 'screen-sm': 768px, + 'screen-md': 992px, + 'screen-lg': 1200px +); +// keywords +$media-expressions: ( + 'screen': 'screen', + 'print': 'print', + 'handheld': 'handheld', + 'landscape': '(orientation: landscape)', + 'portrait': '(orientation: portrait)', + 'retina2x': '(-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi)', + 'retina3x': '(-webkit-min-device-pixel-ratio: 2.5), (min-resolution: 240dpi)' +);*/ + +// ligatured operators ≥ ≤ + +@import 'base/include-media'; +@import "bootstrap-custom"; +@import "bootstrap/variables"; +@import "bootstrap/mixins"; +@import 'base/mixins'; +@import 'base/helpers'; + +//------------------Keypad and global------- + +a{ + &:hover, + &:focus, + &:active, + &.active{ + color: #777; + text-decoration: none; + } +} + +button{ + outline-color:white; +} + +h2{ + margin: 0; +} + +h3{ + margin: 0; +} + +.container{ + width: 400px; + background: #ccc; + padding: 25px; +} + +.top-radius{ + border-top-left-radius: 30px; + border-top-right-radius: 30px; +} + +.bottom-radius{ + border-bottom-left-radius: 30px; + border-bottom-right-radius: 30px; +} + +.header{ + text-align: center; + margin-top:20px; + +} +.main-nav{ + display:flex; + justify-content: space-between; +} + +.keypad-holder{ + width: 250px; + margin: 0 auto; + display: flex; + flex-wrap: wrap; + justify-content: space-around; +} + +.number{ + margin: 0 auto; + width: 200px; + height: 50px; + margin-bottom: 20px; + border-bottom: 1px solid #eee; + display: flex; + justify-content: space-between; + .numbers { + align-self: flex-end; + } +} + +.key{ + width: 60px; + min-height: 60px; + padding: 15px 0; + border-radius: 50%; + border: 1px solid #fff; + text-align: center; + margin-bottom: 10px; + margin-left:5px; + margin-right:5px; + display: flex; + align-items: center; + justify-content: center; + &:last-child{ + background: #4cda64; + color: #fff; + &:hover{ + background: #348f3c; + border: 1px solid #fff; + } + } + &:hover{ + background: rgba(0,0,0, 0.1); + border: 1px solid #777; + } +} + +.tab{ + display: flex; + flex-direction: column; + align-items: center; + &:hover, + &:focus, + &:active, + &.active{ + color: #777; + text-decoration: none; + } +} + +.tab-text{ + font-size: 12px; + margin-top: 5px; +} + +// ------------Search--------------------- + +.contacts tbody{ + display: block; + max-height: 300px; + overflow: hidden; + overflow-y: auto; +} + +.contacts tr{ + display: table; + width: 100%; + table-layout: fixed; +} + +.table > thead > tr > th{ + border: none; +} + +.form-inline .form-control{ + margin-bottom: 10px; + width:100%; +} + +.form-inline .form-group{ + display:block; +} + +//-----------------User-------------------- + +.user-top-line{ + display: flex; + justify-content: space-between; + margin-bottom: 20px; + font-size: 14px; +} + +.user-img{ + width: 100px; + height: 100px; + margin-bottom: 10px; +} + +.user-name{ + font-weight: bold; + text-align: center; + margin-bottom: 30px; +} + +.options-line{ + display: flex; + justify-content: space-between; + text-align: center; + margin-bottom: 20px; +} + +.options-icon{ + border-radius: 50%; + background: #000; + color: #fff; + width: 40px; + height: 40px; + margin: 0 auto 5px; + position: relative; + &:hover{ + background: #777; + } +} + +.options-text{ + font-size: 12px; +} + +.message, .call, .video, .mail{ + display: flex; + flex-direction: column; +} + +.icon{ + @include xycenter; +} + +.tel-number{ + margin-bottom: 20px; + font-size: 16px; +} + +.options-table{ + margin-bottom: 20px; + font-size: 16px; +} + +.options-item{ + padding: 10px 0; + border-bottom: 1px solid #777; + &:first-child{ + border-top: 1px solid #777; + } +} + +//-----------------Edit contact------------ +.scroll-holder{ + max-height: 300px; + overflow: hidden; + overflow-y: auto; + margin-bottom: 20px; +} + +.add-foto-btn{ +width:100px; +height:100px; +border-radius: 50%; +border: 1px solid #eee; +outline: none; +} + +.add-btn{ + border:0; + background:transparent; + width:100%; + text-align:left; + outline-color: gray; +} + +.done-btn{ + background: transparent; + border: none; + outline: none; + &:hover{ + color: #777; + } +} + +.main-info-holder{ + flex-grow:1; +} + +.edit-main-info{ + display: flex; + justify-content: space-between; + margin-bottom: 20px; +} + +.edit-foto{ + margin-right: 10px; +} + +.edit-field{ + padding: 5px 0; + border-bottom: 1px solid #fff; + width:100%; + display: flex; +} + +.delete-btn{ + color: #e32910; + margin-top: 3px; + margin-right: 10px; + border:none; + background:transparent; + &:hover, + &:focus, + &:active,{ + color:#a70b0f; + } +} + +.add-btn{ + color: #4cda64; + margin-top: 3px; + margin-right: 10px; + &:hover, + &:focus, + &:active,{ + color:#348f3c; + } +} + +.delete-contact{ + color: #e32910; + margin: 0 auto; + font-weight: bold; +} \ No newline at end of file