diff --git a/css/base.css b/css/base.css index 4c3ed852..0224ad66 100644 --- a/css/base.css +++ b/css/base.css @@ -2,6 +2,8 @@ box-sizing: border-box; padding: 0; margin: 0; + font-family: Roboto, Arial, sans-serif; + text-rendering: optimizeLegibility; } /* Basic elements defined */ @@ -9,7 +11,6 @@ html, body { height:100%; width:100%; - font-family: 'Roboto', Arial, sans-serif; font-size: 10pt; color: #666; overflow: hidden; @@ -173,6 +174,42 @@ form { font-size: 1.2rem; } +/* BEGIN Popup/dropdown menu */ + +ul.menu { + position: absolute; + left: -1.5rem; + top: 1.5rem; + list-style: none; + width: 12rem; + border-radius: 0.2rem; + padding-top: 0.35rem; + padding-bottom: 0.35rem; + box-shadow: 0 1px 6px rgba(0, 0, 0, .45); + background-color: white; + z-index: 3; +} + +ul.menu > li { + padding: 0.75rem 0.75rem 0.75rem 1.25rem; +} + +ul.menu > li:hover { + background-color: #f5f5f5; +} + +ul.menu > li.separator { + padding: 0; + margin: 2px; + height: 1px; + background-color: #ccc; +} + +ul.menu > li.separator:hover { + background-color: #ccc; +} +/* END popup/dropdown menu */ + /* END of basic elements */ #mountPoint { @@ -297,18 +334,22 @@ form { /* Area which displays an error message */ #alert-box { - padding: 0.5em; background-color: transparent; + height: 0; + overflow: hidden; } #alert-box.error-alert { + padding: 0.5em; background-color: #ffcdd2; border-color: #d32f2f; - color: #600; + color: #b71c1c; + height: auto; } /* List of contacts */ #contacts-panel { overflow-y: auto; + margin-top: 1rem;: margin-bottom: auto; height: 100%; } @@ -317,16 +358,15 @@ form { list-style: none; } -#contact-box li { +#contact-box > li { display: flex; padding: 0.35rem 0.25rem 0.5rem 0.35rem; min-height: 3.5rem; - overflow: hidden; } -#contact-box li:hover { +#contact-box > li:hover { background-color: #eceff1; /* blue-gray 50 highlight */ } -#contact-box li.selected { +#contact-box > li.selected { background-color: #cfd8dc; /* blue-gray 100 highlight */ } @@ -334,6 +374,26 @@ form { font-size: 1.05rem; } +/* The down arrow head - trigger for context menu in contacts list */ +#contact-box li .menuTrigger { + position: relative; + margin-left: auto; + width: 1.5rem; + height: 1.5rem; + border-radius: 50%; + background-color: transparent; +} +#contact-box li:hover .menuTrigger { + background-color: white; +} + +#contact-box li .menuTrigger a { + color: transparent; +} +#contact-box li:hover .menuTrigger a { + color: #757575; +} + /* Avatar and online-offline indicator */ #contact-box .avatar-box { margin-right: 0.35rem; @@ -363,26 +423,33 @@ form { #contact-box .offline { color: #888; } +/* END of avatar box */ #add-topic { position: absolute; right: 1em; bottom: 1em; } -/* END of avatar box */ -/* New account panel (signup form) */ -#signup-form { +/* Various forms inside a panel (signup form, new topic form) */ +.panel-form { display: flex; flex-direction: column; } -#signup-form-one { +.panel-form-one { display: flex; - justify-content: space-between; + justify-content: flex-start; align-items: center; } +.panel-form-two { + display: flex; + flex-direction: column; + width: 100%; +} + + /* Styling of avatar upload area in the New Account panel */ .avatar-upload { position: relative; @@ -698,9 +765,69 @@ form { padding: 0.35rem 1.5rem; } - /* END of modal dialog */ +/* BEGIN Tabs */ +.tabbar { + padding-top: 1rem; + background: #eeeeee; /* #2196f3; */ +} + +.tabbar > ul { + list-style:none; + margin:0; + width:100%; + overflow:hidden; + padding:0; + margin-left:1rem; +} + +.tabbar > ul > li { + float: left; +} + +.tabbar > ul > li > a { + position: relative; + text-decoration: none; + text-transform: uppercase; + display: block; + min-width: 6rem; + text-align: center; + line-height: 2rem; + font-weight: 400; + font-size: 1.1rem; + color: rgba(33,150,243,0.6); + overflow: hidden; + box-shadow: inset 0 -2px 2.5px -1px rgba(0,0,0,0.5); +} + +.tabbar li.active > a { + color: #2196f3; +} + +.tabbar li.active > a:after { + height:0.15rem; + display:block; + content:""; + bottom:0; + left:0; + position: absolute; + background: #2196f3; + animation: tabbar-border-expand 0.2s cubic-bezier(0.4, 0.0, 0.4, 1) 0s alternate forwards; +} + +@keyframes tabbar-border-expand { + 0% { + opacity:0; + width:0; + } + 100% { + opacity:1; + width:100%; + } +} +/* END Tabs */ + /* Miscellaneous */ /* Badge with the number of unread messages */ diff --git a/index.html b/index.html index 9aaaa5c1..b9fe6ed2 100644 --- a/index.html +++ b/index.html @@ -27,8 +27,8 @@ var APP_NAME = "TinodeReact/0.6"; // Host name and port to connect to - var HOST = "api.tinode.co"; - // var HOST = "localhost:6060"; + // var HOST = "api.tinode.co"; + var HOST = "localhost:6060"; // API key. Use https://github.com/tinode/chat/tree/master/keygen to generate your own var API_KEY = "AQEAAAABAAD_rAp4DJh05a1HAwFT3A6K"; @@ -66,6 +66,68 @@ return photo ? 'data:image/' + photo.type + ';base64,' + photo.data : null; } + /* BEGIN Popup/dropdown menu */ + var Menu = React.createClass({ + getInitialState: function() { + return { + visible: this.props.visible + }; + }, + componentWillUnmount: function() { + document.removeEventListener('mousedown', this.handlePageClick, false); + }, + componentWillReceiveProps: function(nextProps) { + this.setState({visible: nextProps.visible}); + if (nextProps.visible) { + document.addEventListener('mousedown', this.handlePageClick, false); + } else { + document.removeEventListener('mousedown', this.handlePageClick, false); + } + }, + handlePageClick: function(e) { + if (ReactDOM.findDOMNode(this).contains(e.target)) { + console.log("click in menu, exiting"); + return; + } + e.stopPropagation(); + document.removeEventListener('mousedown', this.handlePageClick); + console.log("click outside of menu"); + this.setState({visible: false}); + }, + handleClick: function(e) { + e.preventDefault(); + e.stopPropagation(); + this.setState({visible: false}); + this.props.onResult(this.props.topic, this.props.items[e.currentTarget.dataset.id]); + }, + render: function() { + if (!this.state.visible) { + return null; + } + var count = 0; + var menuItems = []; + var instance = this; + this.props.items.map(function(item) { + if (item === "-") { + menuItems.push( +
  • + ); + } else { + menuItems.push( +
  • {item}
  • + ); + } + count++; + }); + return ( + + ); + } + }); + /* END Popup/dropdown menu */ + /* BEGIN Login: a login form */ var LoginView = React.createClass({ getInitialState: function() { @@ -179,9 +241,9 @@ } return (
    -
    -
    -
    + +
    +
    - +
    @@ -202,7 +264,7 @@ } }); - CreateAccountView.AvatarUpload = React.createClass({ + var AvatarUpload = React.createClass({ getInitialState: function() { return { dataUrl: null @@ -248,6 +310,65 @@ }); /* END Account registration */ + /* BEGIN Create new topic and invite users or send an invite */ + var NewTopicView = React.createClass({ + getInitialState: function() { + return { + tabSelected: "p2p", + fn: '', // full/formatted name + imageDataUrl: null + }; + }, + handleTabClick: function(e) { + e.preventDefault(); + this.setState({tabSelected: e.currentTarget.dataset.id}); + }, + handleFnChange: function(e) { + this.setState({fn: e.target.value}); + }, + handleImageChanged: function(img) { + this.setState({imageDataUrl: img}); + }, + handleGroupSubmit: function(e) { + e.preventDefault(); + }, + render: function() { + var submitClasses = "blue"; + if (this.props.disabled) { + submitClasses += " disabled"; + } + return ( +
    +
    + +
    + {this.state.tabSelected === "grp" ? + +
    +
    + +
    + +
    +
    + +
    + + : null} +
    + ); + } + }); + /* END Create new topic and invite users or send an invite */ + /* BEGIN Tinode config panel */ var SettingsView = React.createClass({ getInitialState: function() { @@ -292,7 +413,7 @@ }); /* END Tinode config panel */ - /* BEGIN Manage side panel - handle Login, Account Registration, and Contacts views */ + /* BEGIN Manage side panel - handle Login, Account Registration, Contacts, NewTopic views */ var SideNavbar = React.createClass({ render: function() { var avatar = null; @@ -307,14 +428,17 @@
    {this.props.title}
    {this.props.state === 'login' ? : - this.props.state === 'settings' ? - : - this.props.state === 'edit-account' ? - : - this.props.state === 'register' ? - : - this.props.state === 'contacts' ? - : null} + this.props.state === 'settings' ? + : + this.props.state === 'edit-account' ? + : + this.props.state === 'register' ? + : + this.props.state === 'contacts' ? + : + this.props.state === 'new-topic' ? + : + null}
    ); } @@ -340,10 +464,14 @@ } }) - SideNavbar.MenuSettings = React.createClass({ + SideNavbar.MenuContacts = React.createClass({ render: function() { return ( - settings +
    + add_circle +   + settings +
    ); } }) @@ -376,6 +504,10 @@ case 'contacts': title = this.props.title; avatar = this.props.avatar; + break; + case 'new-topic': + title = "Start New Chat"; + break; default:; }; return ( @@ -384,6 +516,7 @@ title={title} avatar={avatar} onSignUp={this.props.onSignUp} onSettings={this.props.onSettings} + onNewTopic={this.props.onNewTopic} onCancel={this.props.onCancel} /> : + onTopicSelected={this.props.onTopicSelected} /> : + + this.props.state === 'new-topic' ? + : + null}
    ); @@ -446,35 +585,33 @@ /* END EditAccount */ /* BEGIN Contact list (list of topics) */ - - /* A form for initiating a new topic - var NewTopic = React.createClass({ - render: function() { - return ( -
    - - - -
    - ); - } - }); - */ - /* A single topic */ + var contactMenuItems = ["Delete chat", "Mute", "Block"]; var Contact = React.createClass({ - handleClick: function() { - this.props.onSelected(this.props.topic); - }, - render: function() { - var title = this.props.title; - if (title.length > 30) { - title = title.substring(0, 28) + "..."; - } - var selected = (this.props.selected === this.props.topic) ? "selected" : null; - var avatar = this.props.avatar ? + getInitialState: function() { + return { + menuShownFor: null + }; + }, + handleClick: function() { + this.props.onSelected(this.props.topic); + }, + handleContextClick: function(e) { + e.preventDefault(); + e.stopPropagation(); + this.setState({menuShownFor: this.props.topic}); + }, + handleMenuAction: function(topic, action) { + console.log("Menu clicked '" + action + "' for topic " + topic); + this.setState({menuShownFor: null}); + }, + render: function() { + var title = this.props.title; + if (title.length > 30) { + title = title.substring(0, 28) + "..."; + } + var selected = (this.props.selected === this.props.topic) ? "selected" : null; + var avatar = this.props.avatar ? : (Tinode.getTopicType(this.props.topic) === "grp") ? group : @@ -489,9 +626,18 @@ {title}

    {this.props.comment}

    + + + arrow_drop_down + + + ); - } + } }); /* The counter of unread messages in the topic */ @@ -573,9 +719,6 @@ - ); } @@ -1046,7 +1189,7 @@ } }, handleNewTopic: function() { - this.setState({dialogSelected: 'new-topic'}); + this.setState({sidePanelSelected: 'new-topic'}); }, handleDialogCancel: function() { this.setState({dialogSelected: null});