@kano/web-components
Version:
Shared web-components for the Kano ecosystem.
558 lines (535 loc) • 19.1 kB
JavaScript
/**
Example:
<kano-user-menu assets-path="../assets/icons/"
default-avatar="../assets/avatar.png"
user="[[user]]"
xp="100"></kano-user-menu>
@group Kano Elements
*/
/*
FIXME(polymer-modulizer): the above comments were extracted
from HTML and may be out of place here. Review them and
then delete this comment!
*/
import '@polymer/iron-flex-layout/iron-flex-layout.js';
import '../kano-user-summary/kano-user-summary.js';
import '../kano-notifications/kano-notifications.js';
import '../kano-user-badge/kano-user-badge.js';
import '../kano-drop-down/kano-drop-down.js';
import '../kano-icons/kano-icons.js';
import '../kano-style/kano-style.js';
import { Polymer } from '@polymer/polymer/lib/legacy/polymer-fn.js';
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
import { dom } from '@polymer/polymer/lib/legacy/polymer.dom.js';
Polymer({
_template: html`
<style>
*[hidden] {
display: none ;
}
:host {
position: relative;
z-index: 100;
display: block;
}
:host * {
box-sizing: border-box;
}
button {
appearance: none;
border: 0;
background: transparent;
cursor: pointer;
}
button:focus {
outline: 0;
}
nav {
@apply --layout-flex;
@apply --layout-vertical;
background: transparent;
font-family: var(--font-body);
display: block;
}
ul {
list-style: none;
margin: 0px;
padding: 0px;
}
kano-drop-down {
position: absolute;
top: 50px;
}
#notifications-menu kano-drop-down {
left: 45px;
--kano-drop-down: {
width: 360px;
}
}
#avatar-menu kano-drop-down {
left: 65px;
--kano-drop-down: {
width: 130px;
}
}
:host .kano-user-nav,
:host .auth-menu,
:host .user-menu {
height: 100%;
min-height: 60px;
width: 100%;
}
:host .kano-user-nav,
:host .username {
@apply --layout-flex;
@apply --layout-horizontal;
@apply --layout-justified;
}
:host .user-menu,
:host .auth-menu {
@apply --layout-flex;
@apply --layout-horizontal;
@apply --layout-end-justified;
}
:host .menu-item {
cursor: pointer;
position: relative;
@apply --layout-horizontal;
-webkit-tap-highlight-color: transparent;
}
:host .menu-button {
background-color: transparent;
background: transparent;
border: 0px;
box-shadow: none;
color: var(--color-grey);
cursor: pointer;
display: block;
font-family: var(--font-body);
font-size: 14px;
font-weight: bold;
margin: 0px;
padding: 0px;
text-transform: none;
}
:host .auth-menu .menu-button {
@apply --layout-flex;
@apply --layout-vertical;
@apply --layout-center-justified;
}
:host #notifications-button {
border-radius: 25px;
height: 24px;
padding: 0;
width: 24px;
}
:host #notifications-button .icon {
color: #ffffff;
height: 12px;
margin: auto;
opacity: 0.5;
width: 12px;
}
:host #notifications-button[active] {
background-color: var(--color-carnation);
}
:host #notifications-button[active] .icon {
opacity: 1;
}
:host #user-button {
padding: 8px 0px 8px 8px;
}
:host #notifications-button,
:host #user-button {
@apply --layout-center;
@apply --layout-flex;
@apply --layout-horizontal;
@apply --layout-justified;
}
:host #user-button .username {
margin-left: 12px;
font-weight: 400;
color: var(--color-chateau);
}
:host .menu-item.avatar>.link:focus {
background-color: #eeeeee;
}
:host .menu-item.notifications>.link:focus,
:host .menu-item.settings>.link:focus {
transform: scale(1.3);
background-color: transparent;
}
:host .menu-item a {
@apply --layout-flex;
color: var(--color-grey);
text-align: center;
font-family: var(--font-body);
font-size: 14px;
font-weight: normal;
}
:host #notifications-menu,
:host #settings-menu {
@apply --layout-horizontal;
@apply --layout-center;
@apply --layout-center-justified;
}
kano-user-badge {
--kano-user-badge-progress-color: #ff842a;
}
:host .xp-prefix {
font-size: 0;
line-height: 0;
}
:host .xp-icon {
width: 20px;
}
:host .icon-light {
fill: #ffb300;
}
:host .icon-dark {
fill: #b37e00;
}
:host .xp {
border-bottom: 1px solid var(--color-porcelain);
}
:host .xp-text {
color: var(--color-black);
display: inline-flex;
font-weight: bold;
line-height: 1em;
padding-left: 10px;
text-align: left;
}
:host .drop-down-item {
@apply --layout-horizontal;
@apply --layout-center;
color: var(--color-grey);
padding: 4px 13px;
}
:host .drop-down-item.xp {
padding: 12px 13px 11px 13px;
}
:host a.drop-down-item {
text-decoration: none;
}
:host button.drop-down-item {
width: 100%;
}
:host a.drop-down-item:hover,
:host button.drop-down-item:hover {
background-color: var(--color-porcelain);
color: var(--color-black);
}
:host .drop-down-item iron-icon {
margin-right: 12px;
width: 16px;
}
@media screen and (max-width: 960px) {
:host #user-button .username {
display: none;
}
}
@media screen and (max-width: 768px) {
:host .separator {
@apply --layout-self-stretch;
height: 3px;
width: auto;
margin: 20px 39px;
}
:host li.menu-item {
@apply --layout-wrap;
width: auto;
box-sizing: border-box;
}
:host .link {
color: var(--color-chateau);
margin: 0px;
padding: 20px;
text-align: left;
}
:host .auth-menu .menu-button {
padding: 0px 5px;
}
:host #user-button {
padding: 10px 10px;
}
:host #user-button:hover {
border-left-color: transparent;
}
:host #notifications-drop-down {
top: 56px;
right: -10px;
width: 250px;
}
:host #user-drop-down {
position: absolute;
top: 56px;
right: 0px;
}
}
@media all and (min-width: 768px) {
:host .auth-menu .menu-button {
padding: 0px 20px;
}
}
</style>
<nav id="kano-user-nav" class="kano-user-nav">
<ul class="user-menu" hidden\$="[[!_isAuthenticated(user)]]">
<li id="notifications-menu" class="menu-item notifications">
<div class="link menu-button" id="notifications-button" on-tap="_toggleOpen" active\$="[[unreadNotifications]]">
<iron-icon icon="kano-icons:notification" class="icon notifications"></iron-icon>
</div>
<kano-drop-down id="notifications-drop-down" caret-align="right" on-tap="_hideDropdown">
<kano-notifications mode="[[mode]]" notifications="{{notifications}}" on-notifications-read="_onRead" on-notifications-read-all="_onReadAll" unread-count="{{unread}}" world-root="[[worldRoot]]" username="[[user.username]]"></kano-notifications>
</kano-drop-down>
</li>
<li id="avatar-menu" class="menu-item avatar">
<div class="link menu-button" id="user-button" on-tap="_toggleOpen">
<dom-if>
<template is="dom-if" if="[[userSummary]]">
<kano-user-summary avatar="[[_computeAvatar(user)]]" level="[[level]]" default-avatar="{{defaultAvatar}}" progress="[[progress]]" username="[[user.username]]" xp="[[xp]]"></kano-user-summary>
</template>
</dom-if>
<dom-if>
<template is="dom-if" if="[[!userSummary]]">
<kano-user-badge user="[[user]]" xp="[[xp]]" level="{{level}}" default-avatar="{{defaultAvatar}}" radius="40" stroke-width="4"></kano-user-badge>
<span class="username">[[user.username]]</span>
</template>
</dom-if>
</div>
<kano-drop-down id="user-drop-down" caret-align="right" on-tap="_hideDropdown">
<div class="drop-down-item xp">
<div class="xp-prefix">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 31.3 24.6" style="enable-background:new 0 0 31.3 24.6;" xml:space="preserve" class="xp-icon">
<g>
<g>
<path class="icon-light" d="M27.6,0H3.7C1.7,0,0,1.7,0,3.7v17.1c0,2.1,1.7,3.7,3.7,3.7h23.9c2.1,0,3.7-1.7,3.7-3.7V3.7
C31.3,1.7,29.7,0,27.6,0z"></path>
<g>
<path class="icon-dark" d="M11.6,14l-2.7,4.1c-0.2,0.3-0.5,0.5-0.9,0.5c-0.6,0-1-0.5-1-1c0-0.2,0.1-0.4,0.2-0.6l3.3-4.7L7.4,7.9
C7.2,7.7,7.2,7.5,7.2,7.3c0-0.6,0.5-1,1-1c0.4,0,0.6,0.1,0.8,0.4l2.5,3.8L14,6.7c0.2-0.3,0.5-0.4,0.8-0.4c0.6,0,1,0.5,1,1
c0,0.2-0.1,0.4-0.2,0.6l-3.1,4.4l3.3,4.7c0.1,0.1,0.2,0.3,0.2,0.6c0,0.6-0.5,1-1,1c-0.4,0-0.6-0.2-0.9-0.5L11.6,14z"></path>
<path class="icon-dark" d="M19.5,13.7v3.8c0,0.6-0.5,1-1,1s-1-0.5-1-1V7.4c0-0.6,0.5-1,1-1h3.4c2.1,0,3.7,1.2,3.7,3.6
c0,2.5-1.6,3.7-3.7,3.7H19.5z M19.5,8.2v3.6h2.2c1.1,0,1.8-0.7,1.8-1.8c0-1.2-0.7-1.8-1.8-1.8C21.7,8.2,19.5,8.2,19.5,8.2z"></path>
</g>
</g>
</g>
</svg>
</div>
<div class="xp-text">
[[xp]]
</div>
</div>
<a href\$="[[worldRoot]]/users/[[user.username]]" class="drop-down-item" on-tap="_onProfile">
<iron-icon icon="kano-icons:profile" class="icon"></iron-icon> Profile
</a>
<a href\$="[[worldRoot]]/accounts/settings" class="drop-down-item" on-tap="_onSettings">
<iron-icon icon="kano-icons:settings" class="icon"></iron-icon> Settings
</a>
<a href\$="[[worldRoot]]/admin" class="drop-down-item" hidden\$="[[!_isUserAdmin(user)]]">
Admin
</a>
<button class="drop-down-item" on-tap="_onLogout">
<iron-icon icon="kano-icons:logout" class="icon"></iron-icon> Log out
</button>
</kano-drop-down>
</li>
</ul>
<ul class="auth-menu" hidden\$="[[_isAuthenticated(user)]]">
<li class="menu-item">
<div class="menu-button" on-tap="_login">Log in</div>
</li>
<li class="menu-item">
<div class="menu-button" on-tap="_signup">Sign up</div>
</li>
</ul>
</nav>
`,
is: 'kano-user-menu',
properties: {
/**
* Whether the menu should use online or in-app behavior
*/
mode: {
type: String,
value: 'online'
},
unreadNotifications: {
type: Boolean,
computed: '_unreadNotifications(notifications.*)'
},
/**
* Basepath of the site to allow for testing across multiple environments
*/
siteRoot: {
type: String,
value: 'https://kano.me'
},
/**
* User object containing the information about the authenticated user
*/
user: {
type: Object,
value: null
},
/**
* Amount of experience earned by the authenticated user
*/
xp: {
type: Number,
readOnly: true,
notify: true,
computed: '_computeXP(user.profile.progress.levels)',
value: 0
},
/**
* A list of notifications received by the authenticated user
*/
notifications: {
type: Array,
notify: true
},
/**
* Path to the assets required by this components
*/
assetsPath: {
type: String,
value: '/'
},
/**
* Base path of Kano World. You can use this to test redirections to staging/dev environments
*/
worldRoot: {
type: String,
value: 'https://world.kano.me'
},
/**
* Default avatar for the user badge
*/
defaultAvatar: {
type: String,
value: 'https://s3.amazonaws.com/kano-avatars/judoka-standard.png'
},
/**
* Temporary property to allow component switching for development
*/
userSummary: {
type: Boolean,
value: false
},
progress: {
type: Number,
computed: '_computeProgress(user.profile.progress.levels)',
readOnly: true,
notify: true
},
level: {
type: Number,
computed: '_computeLevel(user.profile.progress.levels)',
readOnly: true,
notify: true
}
},
_computeAvatar(user) {
if (user && user.avatar && user.avatar.urls) {
return user.avatar.urls.character || this.defaultAvatar;
}
return this.defaultAvatar;
},
attached () {
this.listen(document, 'tap', '_closeDropdowns');
},
detached () {
this.unlisten(document, 'tap', '_closeDropdowns');
},
_computeLevel (levels) {
if (!levels) {
return 0;
}
return levels.level;
},
_computeProgress (levels) {
if (!levels) {
return 0;
}
let percent = levels.complete * 100;
return percent.toFixed(2);
},
_computeXP (levels) {
if (!levels) {
return 0;
}
return levels.xp || 0;
},
_isAuthenticated (user) {
return !!user;
},
_closeDropdowns (e) {
var dropDowns = dom(this.root).querySelectorAll('kano-drop-down');
for (var i = 0; i < dropDowns.length; i++) {
dropDowns[i].close();
}
},
_onRead (e) {
this.fire('read', {notification: e.detail});
},
_onReadAll (e) {
this.fire('read-all', {notification: e.detail});
},
_login () {
this.fire('login');
},
_onLogout () {
this.fire('logout');
},
_signup () {
this.fire('signup');
},
_onProfile (e) {
if (this.mode === 'app') {
e.preventDefault();
this.fire('view-profile');
}
},
_onSettings (e) {
if (this.mode === 'app') {
e.preventDefault();
this.fire('view-settings');
}
},
_toggleOpen (e) {
var target = e.currentTarget,
triggerId = target.getAttribute('id'),
dropDowns = dom(this.root).querySelectorAll('kano-drop-down'),
targetDDId,
dropDown;
e.stopPropagation();
if (!triggerId) {
target = target.parentNode;
triggerId = target.getAttribute('id')
}
targetDDId = triggerId.replace('-button', '-drop-down');
dropDown = this.$[targetDDId];
for (var i = 0; i < dropDowns.length; i++) {
if (dropDowns[i] !== dropDown) {
dropDowns[i].close();
}
}
if (dropDown) {
dropDown.toggle();
}
},
_unreadNotifications (notifications) {
return this.notifications.filter(function (notification) {
return !notification.read;
}).length > 0;
},
_isUserAdmin (user) {
return user && user.admin_level > 0;
},
_hideDropdown (e) {
e.stopPropagation();
this.menuOpened = false;
this._closeDropdowns();
}
});