npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@incrowd/widget-components

v1.10.2

Published

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

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.