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

pns-component-library

v1.6.7

Published

A comprehensive Vue 3 component library built with modern development practices, providing reusable UI components, composables, and directives for building robust web applications.

Downloads

608

Readme

PNS Component Library

A comprehensive Vue 3 component library built with modern development practices, providing reusable UI components, composables, and directives for building robust web applications.

🚀 Features

  • 15 Production-Ready Components - Form controls, layout components, and utility components
  • 3 Powerful Composables - Reusable logic for common patterns
  • 2 Custom Directives - Loading states and UI enhancements
  • Vue 3 Composition API - Modern, performant, and type-safe
  • Fully Responsive UI & UX - Components automatically adapt to all screen sizes with optimized mobile-first design and touch-friendly interactions
  • Accessibility - ARIA attributes and keyboard navigation support
  • Customizable Themes - Multiple built-in color schemes

Newly released public components: DropdownMenu, Badge, FloatingActionButton, DynamicColorResponsiveButton, Snackbar.

🆕 What's New

2025-10-28

  • Public release of:
    • DropdownMenu
    • Badge
    • FloatingActionButton
    • DynamicColorResponsiveButton

Import options:

  • Global (plugin):
    • app.use(PnsComponentLibrary) then use components directly in templates.
  • Named import:
    • import { DropdownMenu, Badge, FloatingActionButton, DynamicColorResponsiveButton } from 'pns-component-library'

📦 Installation

npm install pns-component-library

or

npm install pns-component-library@latest

🎯 Quick Start

1. Install and Setup

First, import and register the library in your main.js:

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import PnsComponentLibrary from 'pns-component-library'
import 'pns-component-library/style.css'

const app = createApp(App)
app.use(PnsComponentLibrary)
app.mount('#app')

2. Use Components

<template>
  <div>
    <!-- Basic checkbox -->
    <Checkbox v-model="isChecked" label="Accept terms" />
    
    <!-- Phone input with country selection -->
    <AreaCodePhoneInput v-model="phoneData" />
    
    <!-- Dynamic color responsive button (Recommended) -->
    <DynamicColorResponsiveButton 
      display_name="Click me" 
      button_type="filled" 
      built_in_theme="primary"
      @click-button="handleClick"
    />

    <!-- Legacy button (Deprecated but still works; not maintained). New projects should migrate to DynamicColorResponsiveButton. -->
    <ResponsiveButton @click="handleClick">
      Click me
    </ResponsiveButton>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const isChecked = ref(false)
const phoneData = ref({ area_code: '', phone_number: '' })

const handleClick = () => {
  console.log('Button clicked!')
}
</script>

📚 Components

Form Components

AreaCodePhoneInput

International phone number input with country selection and area code handling. Features automatic country flag display, smart focus management, and a clear button that appears on hover/focus.

Attributes

| Attribute | Description | Type | Default | |-----------|-------------|------|---------| | v-model / modelValue | binding value - object containing area_code (string) and phone_number (string) | {area_code: string, phone_number: string} | {} | | country_options | custom country list with country_name and country_code properties | Array<{country_name: string, country_code: string}> | [] | | enable_backup_country_options | use built-in country list when no custom options provided | boolean | true | | country_filterable | enable country search/filtering in dropdown | boolean | true | | disabled | whether input is disabled | boolean | false |

Events

| Event | Description | Parameters | |-------|-------------|------------| | change | triggers when the binding value changes | {area_code: string, phone_number: string} | | focus | triggers when any part of the input gains focus | {area_code: string, phone_number: string} | | blur | triggers when the entire input loses focus | {area_code: string, phone_number: string} | | clear | triggers when clear button is clicked | {area_code: string, phone_number: string} |

Exposes

| Method | Description | Type | |--------|-------------|------| | focus | focus the appropriate input (country selector if no country selected, phone input if country selected) | () => void | | blur | blur both country selector and phone input | () => void | | clear | clear both country selection and phone number | () => void | | alert | show alert message below the input | (message: string) => void | | error | show error message below the input | (message: string) => void | | removeAlertOrErrorEffect | clear alert/error state and message | () => void |

<template>
  <AreaCodePhoneInput 
    v-model="phoneData" 
    :country_filterable="true"
    @change="handlePhoneChange"
  />
</template>

Deprecated Components

Note: Deprecated components still work and are supported for backward compatibility, but they are no longer maintained. For new development, please migrate to the recommended replacements.

ResponsiveButton (Deprecated, still works)

Legacy feature-rich button component superseded by DynamicColorResponsiveButton.

Attributes

| Attribute | Description | Type | Default | |-----------|-------------|------|---------| | display_name | button text | string | 'button' | | size | button size | 'xsmall' \| 'small' \| 'medium' \| 'large' | 'small' | | width_type | button width type | 'fill-whole' \| 'fit-content' | 'fit-content' | | build_in_theme | color theme | 'outlined-primary' \| 'filled-primary' \| 'text-primary' \| 'outlined-secondary' \| 'filled-secondary' \| 'text-secondary' | 'outlined-primary' | | customized_class | custom CSS class | string | — | | hold | whether button is in hold state | boolean | false | | disabled | whether button is disabled | boolean | false | | dropdown_options | dropdown menu options | array | [] | | is_dropdown_option | whether this is a dropdown item | boolean | false |

Events

| Event | Description | Parameters | |-------|-------------|------------| | click | triggers when button is clicked | (event: MouseEvent) | | update-currently-hovered-dropdown-option | triggers when dropdown option is hovered | (option: object) | | add-nested-dropdown-history-stack | triggers when nested dropdown is navigated | (option: object) | | remove-nested-dropdown-history-stack | triggers when navigating back in nested dropdown | — |

Slots

| Name | Description | |------|-------------| | prefix | content before button text | | suffix | content after button text |

<template>
  <ResponsiveButton 
    display_name="Save Changes"
    size="medium"
    build_in_theme="filled-primary"
    @click="handleSave"
  >
    <template #prefix>
      <img :src="iconsMap['eye_icon']" alt="eye" />
    </template>
  </ResponsiveButton>
</template>

InputBox

Versatile input field with clearable icon, inside/outside label, styled themes, and validation helpers. The component dynamically adjusts border/background/label colors based on state (hover, focus, disabled, alert, error) and supports prefix/suffix slots.

Attributes

| Attribute | Description | Type | Default | |-----------|-------------|------|---------| | v-model / modelValue | bound input value | string | '' | | label | label text (optional) | string | '' | | label_type | where to render the label | 'inside' \| 'outside' | 'outside' | | theme_type | visual styling theme | 'filled' \| 'outlined' | 'outlined' | | customized_theme_color | focus/brand color used for caret color and focus border or underline; any valid CSS color or CSS var | string | '' | | type | native input type | string | 'text' | | placeholder | placeholder text | string | 'Enter' | | clearable | show a clear icon when focused/hovered and value is non-empty | boolean | false | | required | show a required asterisk on the label. Visual-only; does not enforce validation | boolean | false | | disabled | disable the input | boolean | false | | autocomplete | native autocomplete | string | 'off' |

Behavior notes:

  • In filled theme, a bottom inset shadow emulates the underline. In outlined theme, an inset border is used. Colors are derived from:
    • Focus: customized_theme_color (or var(--Schemes-primary) fallback)
    • Hover: #17181C
    • Disabled: var(--disabled-color--) and var(--disabled-button-background-color--)
    • Alert/Error: var(--semantic-alert-color--) / var(--semantic-error-color--)
  • Inside label floats above the input content area and inherits a state color similar to the border color.
  • A message region appears below the field when either #supportingText slot has content or when alert/error is active with a message.
  • #supportingText is neutral and does not adopt alert/error colors; alert/error messages are colored separately.
  • Alert/Error messages are set via exposes: alert(message) / error(message); call removeAlertOrErrorEffect() to clear.
  • Disabled state down-weights message colors.
Events

| Event | Description | Parameters | |-------|-------------|------------| | input | fires on each keystroke | (value: string) | | change | fires on Enter or blur | (value: string) | | focus | input focused | () | | blur | input blurred | () | | clear | clear button clicked | () |

Exposes

| Method | Description | |--------|-------------| | focus | programmatically focus the input | | blur | programmatically blur the input | | alert | show alert state with a message (yellow) | | error | show error state with a message (red) | | removeAlertOrErrorEffect | clear alert/error state |

Slots

| Name | Description | |------|-------------| | prefix | content rendered before the input (e.g., an icon) | | suffix | content rendered after the input (e.g., an icon) | | supportingText | optional helper text rendered below the field (e.g., character counter like 0/100). This is distinct from alert/error messages and uses a neutral color. When alert/error is active, their messages render alongside (with their own colors) |

Tip: For prefix/suffix icons, prefer inline SVG that uses fill="currentColor". This way the icon color automatically follows the component state (hover/focus/disabled/alert/error). If you use <img> sources, they won't inherit color.

Usage
<template>
  <InputBox
    v-model="value"
    label="Email"
    label_type="inside"
    theme_type="outlined"
    customized_theme_color="var(--Schemes-primary)"
    type="email"
    placeholder="[email protected]"
    clearable
    autocomplete="email"
  >
    <template #prefix>
      <img src="/icons/mail.svg" style="width:100%;height:100%"/>
    </template>
  </InputBox>
</template>

<script setup>
import { ref } from 'vue'
const value = ref('')
</script>

#### SingleSelector
Dropdown selector with inside/outside label, themed styling, filtering, and optional remote search. Mirrors `InputBox` visual behavior (hover/focus/disabled/alert/error), and supports prefix/suffix slots plus a neutral `supportingText` message region.

##### Attributes

| Attribute | Description | Type | Default |
|-----------|-------------|------|---------|
| v-model / modelValue | binding value (selected option's `value`) | `string \| number \| boolean \| object \| array` |
| label | label text | `string` | `''` |
| label_type | label position | `'inside' \| 'outside'` | `'outside'` |
| theme_type | visual theme | `'filled' \| 'outlined'` | `'outlined'` |
| customized_theme_color | caret color and focus/brand color; any CSS color or CSS var | `string` | `''` |
| required | show red asterisk next to label | `boolean` | `false` |
| options | options array | `Array<{ value: string \| number \| boolean \| object, label: string, prefix?: string, suffix?: string, prefix_slot_raw_html_content?: string, suffix_slot_raw_html_content?: string, options?: Option[], has_divider?: boolean, disabled?: boolean, is_selected?: boolean }>` | `[]` |
| filterable | enable client-side filter input | `boolean` | `false` |
| remote_search | emit query outward instead of local filtering | `boolean` | `false` |
| disabled | disable selector | `boolean` | `false` |
| placeholder | placeholder when no value | `string` | `'Select'` |
| clearable | show clear icon when there is a value and focused/hovered | `boolean` | `false` |
| loading | loading state (mirrors dropdown's `is_loading`) | `boolean` | `false` |
| loading_text | loading text | `string` | `'Loading'` |
| no_data_text | empty-state text | `string` | `'No data'` |
| filter_only_among_options_value | filter against serialized `value` only | `boolean` | `false` |
| filter_only_among_options_label | filter against `label` only | `boolean` | `false` |

Notes:

- `filter_only_among_options_value` and `filter_only_among_options_label` cannot both be `true`. If both are `true`, filtering uses `value`.
- Option `value` may be primitive or object; selection compares by `value` plus `label` for marking `is_selected` in lists.

##### Events

| Event | Description | Parameters |
|-------|-------------|------------|
| change | triggers when the binding value changes | `(value: any)` |
| remote-search | triggers when `remote_search=true` and user types | `(query: string)` |
| filter-change | triggers whenever filter text changes | `(query: string)` |
| visible-change | dropdown visibility changes | `(visible: boolean)` |
| focus | selector focused | `()` |
| blur | selector blurred | `()` |
| clear | clear button clicked | `()` |

##### Exposes

| Method | Description |
|--------|-------------|
| focus | programmatically focus the selector |
| blur | programmatically blur the selector |
| alert | show alert state with a message (yellow) |
| error | show error state with a message (red) |
| removeAlertOrErrorEffect | clear alert/error state |

##### Slots

| Name | Description |
|------|-------------|
| prefix | content before selector |
| suffix | content after selector |
| supportingText | neutral helper text under the field; alert/error messages render alongside in their own colors |

```vue
<SingleSelector 
  v-model="selected"
  :options="options"
{{ ... }}
  filterable
  clearable
  placeholder="Choose option"
>
  <template #prefix>
    <Icon name="search" />
  </template>
  <template #supportingText>
    <!-- e.g. instruction or character counter; neutral color -->
    Pick one option
  </template>
</SingleSelector>
DropdownMenu (dev-only)

Used internally by components to render options lists.

  • Props: width_type: 'fill-whole'|'fit-content', options (same schema as above), size: 'small'|'medium'|'large', with_box_shadow, is_loading, loading_text, no_data_text.
  • Events:
    • select-dropdown-option(value, label, in_dropdown_level)
    • add-new-shown-nested-dropdown(target_item_props, trigger)
    • remove-shown-nested-dropdown(target_item_props)
    • update-shown-nested-dropdown(target_item_props)
    • click-outside-dropdown-menu(event)

Checkbox

Customizable checkbox with multiple themes and validation support.

Attributes

| Attribute | Description | Type | Default | |-----------|-------------|------|---------| | v-model / modelValue | binding value - true when checked, false when unchecked | boolean | false | | label | checkbox label | string | — | | size | checkbox size | 'small' \| 'medium' | 'medium' | | built_in_theme | built-in color theme | 'green' \| 'primary-blue' \| 'secondary-blue' | 'green' | | customized_theme_color | custom theme color (overrides built_in_theme) | string(any valid CSS color value) | '' | | customized_class | custom CSS class | string | — | | disabled | whether checkbox is disabled | boolean | false | | autofocus | whether to auto focus | boolean | false | | indeterminate | whether checkbox is indeterminate | boolean | false |

Events

| Event | Description | Parameters | |-------|-------------|------------| | change | triggers when the binding value changes | (value: boolean) |

Exposes

| Method | Description | Type | |--------|-------------|------| | focus | focus the checkbox | () => void | | blur | blur the checkbox | () => void | | error | show error message, will automatically use the error theme | (message: string) => void | | removeAlertOrErrorEffect | clear error state | () => void |

<template>
  <!-- the #FF0000 will override the built-in theme color -->
  <Checkbox 
    v-model="agreed" 
    label="I agree to terms" 
    built_in_theme="primary-blue"
    customized_theme_color="#FF0000"
    @change="handleChange"
  />
  <!-- if want to use global defined color as the theme color, can use var() -->
  <Checkbox 
    v-model="agreed" 
    label="I agree to terms" 
    customized_theme_color="var(--Schemes-primary)"
    @change="handleChange"
  />
  <!-- Theme color can be any form of CSS color value. Since if the theme color isn't empty, it will override the built-in theme color. The background color of the checked and indeterminate state, and the mask color of the interaction effects of the checkbox will be the theme color, while the border color of unchecked checkbox will always be hsl(226, 8%, 10%). But when the checkbox is in error state, the theme will be error theme (the border color of unchecked checkbox, the background color of the checked and indeterminate state, and the mask color of the interaction effects of the checkbox will always be --semantic-error-color--) -->
  <Checkbox 
    v-model="agreed" 
    label="I agree to terms" 
    customized_theme_color="hsl(3, 71%, 40%)"
    @change="handleChange"
  />
</template>

GroupCheckbox

Container for managing multiple related checkboxes as a group.

Attributes

| Attribute | Description | Type | Default | |-----------|-------------|------|---------| | v-model / modelValue | binding value - array of selected checkbox labels from child Checkbox components | array | [] |

Events

| Event | Description | Parameters | |-------|-------------|------------| | change | triggers when the binding value changes | (value: array) |

<template>
  <GroupCheckbox v-model="selectedItems">
    <Checkbox v-for="item in options" :key="item" :label="item" />
  </GroupCheckbox>
</template>

<script setup>
const selectedItems = ref(['option1'])
const options = ['option1', 'option2', 'option3']
</script>

#### DropdownMenu
Public dropdown menu primitive with nested dropdown support and responsive behavior (web vs. mobile). The companion `DropdownMenuItem` remains internal and is not exposed publically.

##### Attributes

| Attribute | Description | Type | Default |
|-----------|-------------|------|---------|
| options | menu options tree (see schema below) | `Array<Option>` | `[]` |
| size | menu item sizing | `'small' \| 'medium' \| 'large'` | `'small'` |
| width_type | width behavior | `'fill-whole' \| 'fit-content'` | `'fill-whole'` |
| with_box_shadow | whether the menu container has a shadow | `boolean` | `true` |

Option schema (recursive):

type Option = { value: string, label: string, // Optional visuals (priority: slot > raw HTML > image URL handled inside items) prefix?: string, suffix?: string, prefix_slot_raw_html_content?: string, suffix_slot_raw_html_content?: string, // Nesting options?: Option[], // Visual divider hint for the following item has_divider?: boolean, // Disabled state disabled?: boolean, }


Behavior notes:

- On wide screens (width > 1024), nested dropdowns open as side menus on hover/click with smart positioning.
- On small screens (≤ 1024), a single column flow is used; nested levels replace the list view using an internal history stack and a back affordance.
- The component emits a click-outside event when the user clicks outside of the root dropdown.

##### Events

| Event | Description | Parameters |
|-------|-------------|------------|
| select-dropdown-option | emitted when an option without further nesting is selected | `(value: string, in_dropdown_level: number)` |
| click-outside-dropdown-menu | emitted when clicking outside the root menu | `—` |

##### Usage

```vue
<template>
  <DropdownMenu
    :options="options"
    :size="'small'"
    :width_type="'fill-whole'"
    @click-outside-dropdown-menu="onOutside"
    @select-dropdown-option="onSelect"
  />
</template>

<script setup>
import { DropdownMenu } from 'pns-component-library'

const options = [
  {
    value: 'option1',
    label: 'Option 1',
    options: [
      { value: 'option1.1', label: 'Option 1.1' },
      { value: 'option1.2', label: 'Option 1.2', options: [
        { value: 'option1.2.1', label: 'Option 1.2.1' },
      ]},
    ],
  },
  { value: 'option2', label: 'Option 2' },
]

function onOutside(){ /* handle outside click */ }
function onSelect(value, level){ /* handle select */ }
</script>

#### Radio
Radio button component for single selection from multiple options.

##### Attributes

| Attribute | Description | Type | Default |
|-----------|-------------|------|---------|
| v-model / modelValue | binding value - the `value` of the currently selected radio button in the group | `string` | — |
| value | radio value | `string` | — |
| label | radio label | `string` | — |
| name | group name for radio group | `string` | — |
| size | radio size | `'small' \| 'medium'` | `'medium'` |
| built_in_theme | color theme | `'primary-blue' \| 'secondary-blue' \| 'red'` | `'primary-blue'` |
| customized_class | custom CSS class | `string` | — |
| disabled | whether radio is disabled | `boolean` | `false` |
| autofocus | whether to auto focus | `boolean` | `false` |

##### Events

| Event | Description | Parameters |
|-------|-------------|------------|
| change | triggers when the binding value changes | `(value: string)` |

##### Exposes

| Method | Description | Type |
|--------|-------------|------|
| focus | focus the radio | `() => void` |
| blur | blur the radio | `() => void` |
| alert | show alert message | `(message: string) => void` |
| removeAlertOrErrorEffect | clear alert/error state | `() => void` |

```vue
<!-- Radio Group -->
<Radio v-model="selected" value="option1" name="group1" label="Option 1" />
<Radio v-model="selected" value="option2" name="group1" label="Option 2" />

<!-- Single Radio -->
<Radio v-model="single" value="yes" label="Agree to terms" />

Badge

Notification badge component with customizable size, position, themes, and content display. Features automatic text color calculation for optimal contrast.

Attributes

| Attribute | Description | Type | Default | |-----------|-------------|------|---------| | content | badge content (number or text) | string \| number | '' | | size | badge size | 'small' \| 'medium' \| 'large' | 'small' | | position_type | badge position relative to content | 'top-right' \| 'center-right' | 'top-right' | | customized_top_distance | top offset for top-right position (CSS length) | string | '' | | customized_right_distance | right offset for top-right position (CSS length) | string | '' | | built_in_theme | color theme | 'primary' \| 'secondary' | 'primary' | | customized_theme_color | custom background color (any valid CSS color) | string | '' | | customized_class | custom CSS class | string | '' | | disabled | hide the badge without affecting the wrapped content | boolean | false |

Size Specifications

| Size | Web (px) | Mobile ≤767px (px) | With Content | |------|----------|-------------------|--------------| | small | 6×6 | 6×6 | auto-sized | | medium | 8×8 | 6×6 | auto-sized | | large | 16×16 | 16×16 | auto-sized |

Note: When badge has content, it automatically becomes large with padding (min-width: 20px, height: 20px).

Position Types

| Position | Behavior | |----------|----------| | top-right | Positioned absolutely at top-right corner (0, 0) | | center-right | Inline-flex aligned at right side with 4px gap |

Note: Use padding on the wrapper div to control badge position. The badge positions relative to the wrapper's content.

Position customization (top-right only):

  • You can fine-tune the distance of a top-right badge using customized_top_distance and customized_right_distance. These map to internal CSS variables and only apply when position_type="top-right".
<Badge position_type="top-right" customized_top_distance="8px" customized_right_distance="12px" content="3">
  <div style="padding: 8px;">
    <div>Icon</div>
  </div>
  
</Badge>
Built-in Themes

| Theme | Background | Text Color | |-------|-----------|------------| | primary | #AF251D (red) | #FFFFFF (white) | | secondary | #CFE0FC (light blue) | #083D91 (dark blue) |

Custom Theme Color

When using customized_theme_color, the text color is automatically calculated based on the background lightness:

  • Light backgrounds (lightness > 50%) → dark text
  • Dark backgrounds (lightness ≤ 50%) → light text

The algorithm adjusts lightness by ±60 while preserving hue and saturation for optimal contrast and color harmony.

<template>
  <!-- Basic badge with sizes -->
  <Badge size="small">
    <div style="padding: 4px;">
      <div>Icon</div>
    </div>
  </Badge>

  <!-- Badge with content -->
  <Badge content="5">
    <div style="padding: 8px;">
      <div>Icon</div>
    </div>
  </Badge>

  <!-- Badge with position types -->
  <Badge position_type="top-right" content="3">
    <div style="padding: 8px;">
      <div>Icon</div>
    </div>
  </Badge>

  <!-- Badge with custom color -->
  <Badge customized_theme_color="#10B981" content="New">
    <div style="padding: 8px;">
      <div>Icon</div>
    </div>
  </Badge>

  <!-- Dark background example -->
  <Badge customized_theme_color="hsl(220, 70%, 25%)" content="Dark">
    <div style="padding: 8px;">
      <div>Icon</div>
    </div>
  </Badge>
</template>

Interactive Components

DynamicColorResponsiveButton

Modern button with dynamic color logic and responsive behavior.

Attributes

| Attribute | Description | Type | Default | |-----------|-------------|------|---------| | button_type | button style variant | 'filled' \| 'outlined' \| 'text' \| 'elevated' | 'filled' | | button_border_radius | CSS border-radius value | string | '100px' | | built_in_theme | built-in color theme | 'primary' \| 'secondary' \| 'tertiary' \| 'primary-container' \| 'secondary-container' \| 'tertiary-container' | 'primary' | | customized_theme_color | overrides built_in_theme with any CSS color (e.g., #3d5c8f, rgb(...), hsl(...), var(--Schemes-primary)) | string | '' | | customized_class | custom CSS class for outer container | string | '' | | width_type | width mode | 'fill-whole' \| 'fit-content' (auto-handles icon-only with narrow/wide) | 'fit-content' | | button_id | explicit id used in emitted event; falls back to display_name | string | '' | | display_name | button text (used when default slot empty) | string | 'button name' | | prefix | image url for prefix when not using slot | string | '' | | suffix | image url for suffix when not using slot | string | '' | | prefix_slot_raw_html_content | raw html for prefix (when not using named slot) | string | '' | | suffix_slot_raw_html_content | raw html for suffix (when not using named slot) | string | '' | | disabled | disable interaction | boolean | false | | has_interaction_effect | enable hover/focus/active visual effects (mask and slight radius changes). Set to false to keep the button static (useful when embedding purely for coloring icons/slots) | boolean | true | | size | size (Web/Tablet enum) | 'small' \| 'medium' \| 'large' \| 'xlarge' | 'small' |

Events

| Event | Description | Parameters | |-------|-------------|------------| | click-button | emitted on button click | (button_id: string, display_name: string) |

Exposes

| Method | Description | Type | |--------|-------------|------| | handleFocusButton | programmatically apply focus style | () => void | | handleBlurButton | remove focus style | () => void |

Slots

| Name | Description | |------|-------------| | prefix | content before the label (e.g., icon) | | default | label content; falls back to display_name | | suffix | content after the label (e.g., icon) |

<template>
  <DynamicColorResponsiveButton 
    button_id="save-changes"
    display_name="Save Changes"
    button_type="filled"
    built_in_theme="primary"
    :has_interaction_effect="true"
    width_type="fit-content"
    @click-button="onSave"
  >
    <template #prefix>
      <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M5 12h14v2H5z"/></svg>
    </template>
  </DynamicColorResponsiveButton>
</template>

<script setup>
function onSave(id, name){
  console.log('clicked:', id, name)
}
</script>

FloatingActionButton

Mobile-only floating action button that anchors to the bottom-right of the viewport on small screens. It supports three sizes, built-in themes or a custom theme color, named slots for icons, and an optional one-level options menu. The button is automatically hidden on wide screens (when window.width > 1024).

Attributes

| Attribute | Description | Type | Default | |-----------|-------------|------|---------| | display_name | button text | string | 'button' | | size | button size | 'small' \| 'medium' \| 'large' | 'small' | | build_in_theme | built-in color theme | 'primary' \| 'secondary' \| 'tertiary' \| 'primary-container' \| 'secondary-container' \| 'tertiary-container' | 'primary' | | customized_theme_color | custom theme color (overrides built_in_theme). Accepts any valid CSS color string (e.g., #3d5c8f, rgb(61, 92, 143), hsl(217, 40%, 40%), or var(--Schemes-primary)). | string | '' | | customized_class | custom CSS class for the outer container | string | '' | | options | optional menu options (one level). Each item is { value: any, label: string, prefix?: string, suffix?: string }. prefix/suffix are icon URLs for the option. | array | [] |

Notes

  • When options is non-empty, clicking the FAB toggles the options menu. A cross button is provided to close the menu.
  • The component is hidden on wide screens (width > 1024) by design to target mobile usage.
  • customized_theme_color overrides build_in_theme. If it is not provided (empty string), the build_in_theme color will be used instead.
  • customized_theme_color accepts any valid CSS color value: e.g. hsl(217, 40%, 40%), #ff0000, rgb(255, 0, 0), rgba(255, 0, 0, 0.5), hsla(217, 40%, 40%, 0.5), or var(--Schemes-primary).
  • When using customized_theme_color:
    • The FAB background color uses the provided value directly.
    • Related colors (text color, inline SVG color via currentColor, menu cross button colors, menu child button colors, and interaction masks) are dynamically calculated based on the customized_theme_color to ensure contrast and visual harmony.
Events

| Event | Description | Parameters | |-------|-------------|------------| | floating-action-button-click | emitted when the floating action button is clicked. If options is provided, this also toggles the menu visibility. | — | | option-click | emitted when a menu option is clicked | (value: any) |

Slots

| Name | Description | |------|-------------| | prefix | content rendered before the button text. Commonly used for an icon. | | default | default slot for button label content. Falls back to display_name when empty. | | suffix | content rendered after the button text. Commonly used for an icon. |

<script setup>
const quickCreateOptions = [
  { value: 'photo', label: 'New Photo' },
  { value: 'video', label: 'New Video' },
  { value: 'doc', label: 'New Doc' },
]

const handleFabClick = () => {
  console.log('FAB clicked')
}

const handleOptionClick = (val) => {
  console.log('Option clicked:', val)
}
</script>

<template>
  <!-- Will only be visible on small screens (hidden on width > 1024) -->
  <FloatingActionButton
    display_name="New"
    size="small"
    build_in_theme="tertiary"
    :options="quickCreateOptions"
    @floating-action-button-click="handleFabClick"
    @option-click="handleOptionClick"
  >
    <template #prefix>
      <!-- Example icon (uses currentColor to adapt) -->
      <!-- Pros of using inline SVG:
           - Inherits currentColor automatically, so it adapts to theme/text color without extra CSS
           - Reacts naturally to hover/active color-mix effects used by the component
           - No additional network request for external assets
         Using a regular <img src="..."> is also fine if you prefer image assets;
         just note that <img> won't inherit currentColor by default (you'd need pre-colored assets or additional CSS). -->
      <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
        <path d="M6 16L10 12.95L14 16L12.5 11.05L16.5 8.2H11.6L10 3L8.4 8.2H3.5L7.5 11.05L6 16ZM10 20C8.61667 20 7.31667 19.7375 6.1 19.2125C4.88333 18.6875 3.825 17.975 2.925 17.075C2.025 16.175 1.3125 15.1167 0.7875 13.9C0.2625 12.6833 0 11.3833 0 10C0 8.61667 0.2625 7.31667 0.7875 6.1C1.3125 4.88333 2.025 3.825 2.925 2.925C3.825 2.025 4.88333 1.3125 6.1 0.7875C7.31667 0.2625 8.61667 0 10 0C11.3833 0 12.6833 0.2625 13.9 0.7875C15.1167 1.3125 16.175 2.025 17.075 2.925C17.975 3.825 18.6875 4.88333 19.2125 6.1C19.7375 7.31667 20 8.61667 20 10C20 11.3833 19.7375 12.6833 19.2125 13.9C18.6875 15.1167 17.975 16.175 17.075 17.075C16.175 17.975 15.1167 18.6875 13.9 19.2125C12.6833 19.7375 11.3833 20 10 20Z" fill="currentColor"/>
      </svg>
    </template>
  </FloatingActionButton>

  <!-- Custom theme color overrides built-in theme -->
  <FloatingActionButton
    display_name="Custom"
    size="medium"
    customized_theme_color="hsl(217, 40%, 40%)"
    @floating-action-button-click="handleFabClick"
  />
</template>

Feedback Components

SimplifiedNotification

Toast notification component for user feedback.

Attributes

| Attribute | Description | Type | Default | |-----------|-------------|------|---------| | content | notification content | string | — | | build_in_theme | color theme | 'blue' \| 'red' \| 'yellow' \| 'green' | 'blue' | | title | notification title | string | — | | customized_alert_icon_src | custom icon URL | string | — | | customized_class | custom CSS class | string | — | | with_close_btn | whether to show close button | boolean | true | | mounted_programmatically | whether mounted programmatically | boolean | false | | hide_after | auto-hide after milliseconds (0 = never) | number | 0 |

Events

| Event | Description | Parameters | |-------|-------------|------------| | close | triggers when notification is closed | — |

Slots

| Name | Description | |------|-------------| | content | custom notification content |

<SimplifiedNotification
  title="Success"
  content="Operation completed successfully"
  build_in_theme="green"
  :hide_after="3000"
  @close="handleClose"
/>

Snackbar

Bottom-positioned snackbar component with optional action button, auto-dismiss, and responsive layout. Supports both static usage and programmatic creation via FloatingSnackbar composable.

Attributes

| Attribute | Description | Type | Default | |-----------|-------------|------|---------| | content | snackbar message text | string | — | | action | action button label (optional) | string | — | | padding_size | padding size | 'normal' \| 'large' | 'normal' | | with_close_icon | show close icon | boolean | true | | customized_class | custom CSS class | string | — | | mounted_programmatically | internal flag for programmatic mounting | boolean | false | | hide_after | auto-hide after milliseconds (0 = never, default: 3000) | number | 3000 |

Events

| Event | Description | Parameters | |-------|-------------|------------| | close | triggers when snackbar is closed | — | | action | triggers when action button is clicked | — |

Slots

| Name | Description | |------|-------------| | default | custom snackbar content (overrides content prop) |

Layout Behavior
  • Row Layout (default): Content on left, action button + close icon on right
    • Used when action button text is ≤10 characters
  • Stacked Layout (column): Content above, action button + close icon below (right-aligned)
    • Automatically triggered when action button text is >10 characters
    • Prevents text overflow and maintains readability
Padding Specifications

| Size | Top/Bottom | Left | Right | |------|-----------|------|-------| | normal | 0px | 16px | 0px | | large | 10px | 16px | 0px |

Note: Text content has internal padding of 14px top/bottom and 14px right for proper spacing.

Usage
<template>
  <!-- Basic snackbar with action -->
  <Snackbar 
    content="File deleted successfully"
    action="Undo"
    :with_close_icon="true"
  />

  <!-- Snackbar without action button -->
  <Snackbar 
    content="Changes saved"
    :with_close_icon="true"
  />

  <!-- Snackbar with larger padding -->
  <Snackbar 
    content="Snackbar with larger padding"
    action="Action"
    padding_size="large"
  />

  <!-- Snackbar with long action text (auto stacked layout) -->
  <Snackbar 
    content="This will use column layout"
    action="Long Action Button"
  />

  <!-- Disable auto-hide -->
  <Snackbar 
    content="This will not auto-hide"
    action="Dismiss"
    :hide_after="0"
  />

  <!-- With event handlers -->
  <Snackbar 
    content="Click action to see event"
    action="Click Me"
    @action="handleAction"
    @close="handleClose"
  />
</template>

<script setup>
function handleAction() {
  console.log('Action clicked')
}

function handleClose() {
  console.log('Snackbar closed')
}
</script>
FloatingSnackbar Composable

For programmatic snackbar creation (recommended approach):

import { FloatingSnackbar } from 'pns-component-library'

// Basic usage
FloatingSnackbar({
  content: 'File deleted successfully',
  action: 'Undo',
  with_close_icon: true,
  onAction: () => {
    console.log('Undo clicked')
  },
  onClose: () => {
    console.log('Snackbar closed')
  }
})

// Without auto-hide
FloatingSnackbar({
  content: 'This will not auto-hide',
  action: 'Dismiss',
  hide_after: 0
})

// With custom slot content
FloatingSnackbar({
  action: 'Action',
  with_close_icon: true
}, {
  default: () => h('div', { style: { fontWeight: 'bold' } }, 'Custom content')
})

WholePageErrorPopup

Full-page error overlay for critical error states.

Attributes

| Attribute | Description | Type | Default | |-----------|-------------|------|---------| | title | error title | string | 'Error' | | content | error message | string | 'An error has occured' |

Events

| Event | Description | Parameters | |-------|-------------|------------| | retry | triggers when retry button is clicked | — |

Slots

| Name | Description | |------|-------------| | content | custom error content |

<WholePageErrorPopup 
  title="Connection Error"
  content="Unable to connect to server. Please check your internet connection."
  @retry="handleRetry"
/>

🔧 Composables

FloatingNotification

Programmatically create floating notifications.

Parameters

| Parameter | Description | Type | Default | |-----------|-------------|------|---------| | type | notification type | 'success' \| 'error' \| 'warning' \| 'info' | 'info' | | message | notification message | string | — | | duration | auto-hide duration in milliseconds | number | 3000 | | title | notification title | string | — |

Returns

| Type | Description | |------|-------------| | void | Creates and displays the notification |

import { FloatingNotification } from 'pns-component-library/composables'

// Show notification
FloatingNotification({
  type: 'success',
  message: 'Operation completed!',
  duration: 3000
})

useWindowSize

Reactive window size tracking composable.

Returns

| Property | Description | Type | |----------|-------------|------| | width | current window width | Ref<number> | | height | current window height | Ref<number> |

import { useWindowSize } from 'pns-component-library/composables'

const { width, height } = useWindowSize()

// Use in template or computed
watchEffect(() => {
  console.log(`Window size: ${width.value}x${height.value}`)
})

🎨 Directives

v-component-loading

Add loading state to any component with overlay spinner.

Usage

| Binding | Description | Type | |---------|-------------|------| | value | whether to show loading state | boolean |

<template>
  <div v-component-loading="isLoading">
    Content here
  </div>
</template>

<script setup>
import { ref } from 'vue'
const isLoading = ref(false)

// Toggle loading
const startLoading = () => {
  isLoading.value = true
  setTimeout(() => {
    isLoading.value = false
  }, 2000)
}
</script>

v-whole-page-loading

Full-page loading overlay that covers the entire viewport.

Usage

| Binding | Description | Type | |---------|-------------|------| | value | whether to show full-page loading | boolean |

<template>
  <div v-whole-page-loading="isPageLoading">
    App content
  </div>
</template>

<script setup>
import { ref } from 'vue'
const isPageLoading = ref(false)

// Show page loading
const loadPage = async () => {
  isPageLoading.value = true
  try {
    await fetchData()
  } finally {
    isPageLoading.value = false
  }
}
</script>

🎨 Theming

The library provides multiple built-in themes:

  • Green (default) - Primary green theme
  • Primary Blue - Professional blue theme
  • Secondary Blue - Light blue accent theme
  • Red - Error/warning red theme
<Checkbox built_in_theme="primary-blue" />
<DynamicColorResponsiveButton button_type="filled" built_in_theme="primary" />

📱 Responsive Design

Components automatically adapt to different screen sizes:

  • Desktop: Full feature set with optimal spacing
  • Mobile: Compact layouts with touch-friendly interactions
  • Breakpoint: 767px for mobile/desktop switching

♿ Accessibility

All components include:

  • ARIA attributes for screen readers
  • Keyboard navigation support
  • Focus management
  • High contrast support
  • Semantic HTML structure

🛠 Development

Project Setup

npm install

Development Server

npm run dev

Build for Production

npm run build

Component Documentation

Visit the demo pages to see all components in action with interactive examples.

📄 License

MIT License - see LICENSE file for details.

🤝 Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests and documentation
  5. Submit a pull request

📞 Support

For questions and support, please open an issue on the GitHub repository.


Built with ❤️ using Vue 3 and modern web technologies.