@incrowd/widget-components

A collection of reusable Vue.js components for web widgets.

Stats

StarsIssuesVersionUpdatedCreatedSize
@incrowd/widget-components
1.10.23 years ago3 years agoMinified + gzip package size for @incrowd/widget-components in KB

Readme

Widget Components

A collection of reusable Vue.js components for web widgets.

Motivation

To reuse widget components across all web widget projects.

Requirements

The consuming project must be a Vue.js project with the following:

.vb > .vb-dragger {
    z-index: 5;
    width: 12px;
    right: 0;
}

.vb > .vb-dragger > .vb-dragger-styler {
    -webkit-backface-visibility: hidden;
    backface-visibility: hidden;
    -webkit-transform: rotate3d(0,0,0,0);
    transform: rotate3d(0,0,0,0);
    -webkit-transition:
        background-color 100ms ease-out,
        margin 100ms ease-out,
        height 100ms ease-out;
    transition:
        background-color 100ms ease-out,
        margin 100ms ease-out,
        height 100ms ease-out;
    background-color: rgba(150, 150, 150,.1);
    margin: 5px 5px 5px 0;
    border-radius: 20px;
    height: calc(100% - 10px);
    display: block;
}

.vb.vb-scrolling-phantom > .vb-dragger > .vb-dragger-styler {
    background-color: rgba(150, 150, 150,.3);
}

.vb > .vb-dragger:hover > .vb-dragger-styler {
    background-color: rgba(150, 150, 150,.5);
    margin: 0px;
    height: 100%;
}

.vb.vb-dragging > .vb-dragger > .vb-dragger-styler {
    background-color: rgba(150, 150, 150,.5);
    margin: 0px;
    height: 100%;
}

.vb.vb-dragging-phantom > .vb-dragger > .vb-dragger-styler {
    background-color: rgba(150, 150, 150,.5);
}
.lb-table {
    width: 100%;
    border-collapse: collapse;
}

.lb-footer .lb-table {
    margin: auto 0;
}

.lb-head {
    text-transform: uppercase;
    text-align: center;
    color: #97A0A5;
    background-color: #F1F5F7;
    font-size: 80%;
}

.lb-head-row {
    height: 2.4rem;
}

.lb-point-info-icon {
    cursor: pointer;
    height: 1rem;
    margin-bottom: 0.25rem;
}

.lb-body-row {
    border-bottom: 1px solid #dee2e6;
    color: #232F43;
}

.lb-pundit-row {
    background: linear-gradient(to right, #FFA548 , #FAD961);
}

.lb-pundit-row .lb-body-cell {
    color: #fff;
}

.lb-rank-cell, .lb-streak-cell, .lb-btn-cell {
    width: 4rem;
    text-align: center;
}

.lb-rank-cell-label {
    height: 2rem;
    width: 2rem;
    padding-top: 0.25rem;
    margin-bottom: 0;
    font-size: 1rem;
    font-weight: 700;
}

.lb-rank-cell-p {
    font-size: 1rem;
    font-weight: 700;
}

.lb-name-cell {
    width: 13rem;
    display: flex;
}

.lb-profile-picture {
    border-radius: 50%;
    background-color: #fff;
    border: 1px solid #dee2e6;
    margin: 0.25rem 0;
    height: 3.7rem;
    width: 3.7rem;
}

.lb-star-icon {
    position: absolute;
    min-height: 1.6rem;
    min-width: 1.6rem;
    right: -0.8rem;
    bottom: -0.8rem;
    border-radius: 50%;
    border: 0.1rem solid white;
}

.lb-name-cell-p-container {
    margin: auto 0;
    max-width: 14rem;
}

.lb-name-cell-p {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 1.2rem;
    font-weight: 700;
    margin: auto 0 auto 1.5rem;
}

.lb-name-cell-p-top-player {
    font-size: 1rem;
}

.lb-point-cell {
    width: 5rem;
    text-align: center;
}

.lb-point-cell, .lb-streak-cell {
    font-size: 1.2rem;
    font-weight: 500;
}

.lb-streak-cell-flag {
    width: 2.2rem;
}

.lb-streak-cell-flag-p {
    position: absolute;
    width: 100%;
    bottom: 1.25rem;
}

.lb-btn-cell-btn {
    background-color: #299934;
    cursor: pointer;
    color: #fff;
    border-radius: 0;
    padding: 0.25rem 0.5rem;
    border: 1px solid transparent;
}

.lb-footer {
    height: 5rem;
    background-color: #232F43;
    color: #fff;
    display: flex;
    border-radius: 0.8rem 0.8rem 0 0;
}

.lb-footer .lb-profile-picture {
    margin-top: auto;
    margin-bottom: auto;
}

.lb-footer .lb-btn-cell-btn {
    background-color: #fff;
    color: #4a4a4a;
    border-radius: 0.25rem;
}

Getting Started

npm i @incrowd/widget-components or yarn add @incrowd/widget-components

then in main.js or a JavaScript file that serve as the entry point of the application:

import '@incrowd/widget-components'

It will automatically register all widget components.

Usage

GenericLayout

<generic-layout
    fullBg="/path/to/background/image"
    fullBgColor="colour"
    bodyBg="/path/to/background/image"
    :hideAlert="false">
    <div slot="header">HEADER</div>
    <div slot="subHeader">SUBHEADER</div>
    <div slot="body">BODY</div>
    <div slot="footer">FOOTER</div>
</generic-layout>

Props

Prop Type Default
fullBg String
fullBgColor String
bodyBg String
hideAlert Boolean false

Slots

header and subHeader slots will always be fixed at the top of the widget.

body slot uses vuebar for scrollbar hence the CSS requirement to style it. body will fill up any space left inside the widget between headers and footer.

footer slot will always be fixed at the bottom of the widget.

Alert

An alert box is included as part of the GenericLayout. To use it, it is required to have Vuex store set up and the store must contain the following default state and mutation:

export const state = () => ({
    ...
    alert: null,
    ...
})

export const mutations = {
    ...
    setAlert (state, alert) {
        state.alert = alert
    },
    ...
}

To show an alert, e.g. call store.commit('setAlert', {msg: 'Something went wrong', type: 'error'}). The alert box will then have class of alert-${type}. By default text in alert box is white in colour, to style certain type of alert, add CSS in global scope, for example:

.alert-error {
    background-color: #252525;
}
.alert-success {
    background-color: #299934;
}

By default, the text for dismissing the alert is OK, to replace it, call alert with btnText as part of the object: store.commit('setAlert', {msg: 'Something went wrong', type: 'error', btnText: 'Dismiss'})

The alert will not be shown if hideAlert prop is set to true.


HeaderBar

<generic-layout>
    <header-bar slot="header"
        height="3"
        headerBg="/path/to/background/image"
        bgColor="colour"
        title="title"
        :isTitleWhite="true"
        :hasShadow="false"
        :hasMenu="false">
        <div slot="left">LEFT</div>
        <div slot="mid">MID</div>
        <div slot="right">RIGHT</div>
    </header-bar>
</generic-layout>

Props

Prop Type Default
height* String 5
headerBg String
bgColor String #002672
hasBgShadow Boolean true
title String
isTitleWhite Boolean true
titleClasses** Array
hasShadow*** Boolean false
hasMenu Boolean false
midZIndex String 0

* height in rem

** Array of CSS classes apply to title of header bar

*** If hasShadow is true, class box-shadow will be applied, style it by adding CSS in global scope, for example:

.box-shadow {
    box-shadow: 0 1px 5px 0 rgba(0,0,0,0.2);
}

Slots

Elements inside left slot will be aligned to the left and elements inside right slot will be aligned to the right.

If hasMenu is true, left slot will display menu-icon.png be default hence the requirement of having the image and clicking on it will navigate to /menu page.

By default, text in mid slot is centred and will display title using <h3> with class header-bar-title.


ProfileForm

<template>
    ...
    <profile-form
        :profilePicture.sync="profilePicture"
        :screenName.sync="screenName"
        :firstName.sync="firstName"
        :lastName.sync="lastName"
        labelColor="#0054A5"
        penIconBgColor="#299934"/>
    ...
</template>

<script>
export default {
    data: () => ({
        profilePicture: null,
        screenName: null,
        firstName: null,
        lastName: null
    })
}
</script>

Props

Prop Type Required Default
profilePicture Any Yes
screenName Any Yes
firstName Any Yes
lastName Any Yes
labelColor String No #707070
penIconBgColor String No #000

The profile form consist of four inputs: An image upload and three text fields for the names. The parent component must have data set up as shown above and pass them as props into ProfileForm with .sync. When the values change inside ProfileForm, the corresponding value in data in parent will be updated as well.

Alert

store.commit('setAlert', {msg: 'Image too big', type: 'error'}) will be called if profile image size is greater than 8388607.

Style

The three input fields for name have class form-control, add CSS in global scope to style them.

Images

profile-picture.png will be used as the placeholder image and profile-form-pen-icon.png is the pen icon next to the profile image.


ShareOverlay

<template>
    ...
    <share-overlay
        :showShare.sync="showShare"
        title="Share Title"
        :platforms="platforms"
        :trackShare="trackShare"
        globalShareUrl="URL"/>
    ...
</template>

<script>
export default {
    methods: {
        trackShare (platform) {...}
    },
    data: () => ({
        showShare: false,
        platforms: [
            ...
            {
                network: 'facebook',
                url: 'SHARE_URL',
                title: 'TITLE',
                description: 'DESCRIPTION',
                quote: 'QUOTE'
            }
            ...
        ]
    })
}
</script>

Props

Prop Type Required
showShare Boolean Yes
title String No
platforms Array Yes
trackShare Function No
globalShareUrl String No

.sync is needed for showShare to enable ShareOverlay to update its value, there will be white-cross.png that will set showShare to false when clicked, effectively dismissing the overlay.

title is the title of the overlay displayed above the platform icons.

platforms is an array of objects, each object contains information about the social platform as shown above.

trackShare will be called when a share dialog is opened.

If globalShareUrl exists, it will be used instead of url within each platform.

vue-social-sharing

vue-social-sharing is used, please read the documentation and construct an appropriate platforms array. Please install NPM package vue-social-sharing then in main.js or a JavaScript file that serve as the entry point of the application, add:

import Vue from 'vue'
import SocialSharing from 'vue-social-sharing'
Vue.use(SocialSharing)

Currently ShareOverlay only support the following networks: facebook, twitter, linkedin, googleplus, whatsapp, sms, email, and only title, description, and quote are supported for share text.

Font Awesome 5

Font Awesome 5 icons are required. Inside the template of the component, it uses tag name fa for icons: <fa :icon="p.icon"/>. In a Nuxt.js project, please install NPM packages nuxt-fontawesome, @fortawesome/free-solid-svg-icons and @fortawesome/free-brands-svg-icons, then in nuxt.config.js add:

modules: ['nuxt-fontawesome'],
fontawesome: {
    component: 'fa',
    imports: [
        {set: '@fortawesome/free-solid-svg-icons'},
        {set: '@fortawesome/free-brands-svg-icons', icons: ['faFacebookSquare', 'faTwitterSquare', 'faGooglePlusSquare', 'faLinkedin', 'faWhatsappSquare]}
    ]
}

LeaderboardBody

<template>
    ...
    <leaderboard-body
        :rankings="rankings"
        :handleScroll="handleScroll"
        :pointInfo="pointInfo"
        :btnObj="btnObj"
        type="round"
        typeValue="1"/>
    ...
</template>

<script>
import _ from 'lodash'
export default {
    computed: {
        btnObj () {
            return {
                key: 'KEY',
                text: 'BUTTON_TEXT',
                action: () => { ... }
            }
        }
    },
    methods: {
        handleScroll: _.throttle(function (body) {
            // Your logic
        }, 1000)
    },
    data: () => ({
        rankings: {...},
        pointInfo: 'POINT_INFO'
    })
}
</script>

Props

Prop Type Required Default
rankings Any Yes
handleScroll Function No
pointInfo String No
btnObj Object No
type String No 'season'
typeValue String No

rankings should be an object containing the leaderboard and user objects. The leaderboard object should then contains the users array which will be used to render the leaderboard.

handleScroll is a function that will run when user scroll on the leaderboard. It is recommended to use lodash throttle to limit calls per second.

pointInfo is a string for displaying additional information about the points. If pointInfo exist, info-icon.png will be displayed next to points header and clicking on it will call store.commit('setAlert', {msg: pointInfo, type: 'info'}). Please read above for more information about alert, and add CSS class alert-info in global scope to style.

If btnObj exist, an extra column of buttons will be added to the leaderboard. The key controls whether a user should have a button or not, a button will be shown if user[key] exist. btnObj.text is the text displayed on the button and btnObj.action will be called when the button is clicked.

type can be season, month, or round.

typeValue for season world normally be year such as 2018, round will be a number and so as month, e.g. 8 will be used for August.

Style

Add leaderboard.css in global scope and edit it when needed.

Streak

There is a column for streak and the streak images will be used depending on user's streak.


LeaderboardFooter

<leaderboard-footer :rankings="rankings" :btnObj="btnObj"/>

Props

Prop Type Required Default
rankings Any Yes
btnObj Object No
type String No 'season'
typeValue String No

Same as LeaderboardBody except button won't check for key.

Style

Add leaderboard.css in global scope and edit it when needed.

If you find any bugs or have a feature request, please open an issue on github!

The npm package download data comes from npm's download counts api and package details come from npms.io.