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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pecb-ui/components

v1.1.12

Published

Professional Angular UI Components Library with TypeScript and SCSS by PECB

Readme

@pecb-ui/components

A professional Angular 17 UI components library built with TypeScript and SCSS. Features Storybook for component development and documentation, full accessibility compliance (WCAG 2.1 AA), and tree-shakeable, AOT-compatible builds.

npm version Angular License: MIT

Features

  • 🎯 Angular 17.3+ - Built with the latest Angular features
  • 📦 APF Compliant - Angular Package Format for maximum compatibility
  • 🌳 Tree-shakeable - Only import what you use
  • AOT Compatible - Optimized for Ahead-of-Time compilation
  • WCAG 2.1 AA - Full accessibility compliance
  • 🎨 SCSS Design System - Professional variables, mixins, and utilities
  • 📚 Storybook - Interactive component documentation
  • 🔒 Strict TypeScript - Full type safety

Installation

From npm (recommended)

npm install @pecb-ui/components

From local package

npm install /path/to/dist/ui-components/pecb-ui-components-1.0.0.tgz

Peer Dependencies

This library requires the following peer dependencies:

{
  "@angular/common": "^17.3.0",
  "@angular/core": "^17.3.0",
  "@angular/animations": "^17.3.0"
}

Quick Start

1. Import the Module

// app.module.ts
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { PecbComponentsModule } from "@pecb-ui/components";

import { AppComponent } from "./app.component";

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    PecbComponentsModule, // Import the PECB UI components module
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

2. Import Global Styles (Optional)

If you want to use the PECB design system variables in your application:

// styles.scss
@import "@pecb-ui/components/styles/main";

Or import specific abstracts:

// In your component SCSS
@import "@pecb-ui/components/styles/abstracts/variables";
@import "@pecb-ui/components/styles/abstracts/mixins";

.my-component {
  color: $primary-color;
  padding: $spacing-md;
  @include elevation(2);
}

3. Use Components

<!-- app.component.html -->
<pecb-button variant="primary" size="medium" (clicked)="handleClick()"> Click Me </pecb-button>

<pecb-card elevation="md">
  <pecb-card-header>
    <h3>Welcome</h3>
  </pecb-card-header>
  <pecb-card-body>
    <p>Your content goes here</p>
  </pecb-card-body>
</pecb-card>

Components

Button Component

A versatile button component with multiple variants, sizes, and accessibility features.

<!-- Basic button -->
<pecb-button variant="primary" (clicked)="onClick()"> Submit </pecb-button>

<!-- With loading state -->
<pecb-button variant="primary" [loading]="isLoading"> Save </pecb-button>

<!-- Disabled button -->
<pecb-button variant="danger" [disabled]="true"> Delete </pecb-button>

<!-- Full width button -->
<pecb-button variant="success" [fullWidth]="true"> Continue </pecb-button>

<!-- Icon button with accessibility label -->
<pecb-button variant="secondary" ariaLabel="Close dialog" iconLeft="✕"> </pecb-button>

<!-- Toggle button -->
<pecb-button variant="secondary" [ariaPressed]="isActive" (clicked)="toggle()"> Toggle </pecb-button>

<!-- Button that controls a dropdown -->
<pecb-button variant="primary" [ariaExpanded]="isOpen" ariaHasPopup="menu" ariaControls="dropdown-menu" (clicked)="toggleDropdown()"> Menu </pecb-button>

Properties:

| Property | Type | Default | Description | | -------------- | ------------------------------------------------------------------------------------ | ----------- | ----------------------------------- | | variant | 'primary' \| 'secondary' \| 'success' \| 'danger' \| 'warning' \| 'info' \| 'text' | 'primary' | Visual style variant | | size | 'small' \| 'medium' \| 'large' | 'medium' | Button size | | disabled | boolean | false | Disable the button | | loading | boolean | false | Show loading spinner | | fullWidth | boolean | false | Make button full width | | type | 'button' \| 'submit' \| 'reset' | 'button' | HTML button type | | iconLeft | string | - | Icon before text | | iconRight | string | - | Icon after text | | ariaLabel | string | - | Accessible label | | ariaExpanded | boolean | - | ARIA expanded state | | ariaPressed | boolean | - | ARIA pressed state (toggle buttons) | | ariaHasPopup | boolean \| 'menu' \| 'listbox' \| 'tree' \| 'grid' \| 'dialog' | - | Popup type | | ariaControls | string | - | ID of controlled element |

Events:

| Event | Type | Description | | --------- | -------------------------- | ----------------------- | | clicked | EventEmitter<MouseEvent> | Emitted on button click |

Card Component

A container component for displaying content with optional header, body, and footer sections.

<!-- Basic card -->
<pecb-card elevation="md">
  <pecb-card-header>
    <h3>Card Title</h3>
  </pecb-card-header>
  <pecb-card-body>
    <p>Card content goes here</p>
  </pecb-card-body>
  <pecb-card-footer>
    <pecb-button variant="primary">Action</pecb-button>
  </pecb-card-footer>
</pecb-card>

<!-- Hoverable card -->
<pecb-card elevation="sm" [hoverable]="true">
  <pecb-card-body>
    <p>Hover over me!</p>
  </pecb-card-body>
</pecb-card>

<!-- Card as article (accessible) -->
<pecb-card role="article" ariaLabelledBy="article-title">
  <pecb-card-header>
    <h2 id="article-title">Article Headline</h2>
  </pecb-card-header>
  <pecb-card-body>
    <p>Article content...</p>
  </pecb-card-body>
</pecb-card>

<!-- Card as region -->
<pecb-card role="region" ariaLabel="Product Information">
  <pecb-card-body>
    <p>Product details...</p>
  </pecb-card-body>
</pecb-card>

Properties:

| Property | Type | Default | Description | | ----------------- | ---------------------------------------------------------- | -------- | ------------------------ | | elevation | 'none' \| 'sm' \| 'md' \| 'lg' \| 'xl' | 'md' | Shadow depth | | hoverable | boolean | false | Enable hover effect | | noPadding | boolean | false | Remove default padding | | role | 'article' \| 'region' \| 'group' \| 'listitem' \| 'none' | 'none' | Semantic role | | ariaLabel | string | - | Accessible label | | ariaLabelledBy | string | - | ID of labelling element | | ariaDescribedBy | string | - | ID of describing element |

Video Player Component

The PECB course video player — a fully custom-skinned, accessible HTML5 <video> wrapper that reproduces the Course Player — Content Redesign stage: rounded dark 16:9 surface, poster, brand watermark + section label, centre play button and a bottom control bar (seek bar, play/pause, rewind, volume, time, captions, playback-speed settings, fullscreen).

It is standalone (no icon-registry or service dependencies), responsive through container queries (it adapts to the width of its slot, not the viewport), keyboard operable, touch friendly and themable through CSS custom properties — so it can be dropped into any Angular 17+ project.

import { VideoPlayerComponent } from "@pecb-ui/components";
// or: import { VideoPlayerComponent } from '@pecb-ui/components/data-display';
<!-- Minimal -->
<pecb-video-player src="https://cdn.example.com/lesson.mp4" poster="https://cdn.example.com/lesson.jpg" title="Introduction to management systems — Part 3" label="SECTION 2"> </pecb-video-player>

<!-- Multiple sources + captions -->
<pecb-video-player [src]="[{ src: 'lesson.webm', type: 'video/webm' }, { src: 'lesson.mp4', type: 'video/mp4' }]" [tracks]="[{ src: 'lesson.en.vtt', srclang: 'en', label: 'English', default: true }]" crossOrigin="anonymous" label="SECTION 2"> </pecb-video-player>

<!-- Driven from outside (transcript / lesson list) with two-way bindings -->
<pecb-video-player [src]="lesson.src" [(playing)]="playing" [(currentTime)]="at" [(captionsEnabled)]="captions" (playbackEnded)="markLessonComplete()" (timeUpdate)="saveProgress($event.currentTime)"> </pecb-video-player>

<!-- Project custom overlays (quiz, end-screen…) on top of the stage -->
<pecb-video-player [src]="src">
  <div pecbVideoOverlay class="my-quiz-overlay">…</div>
</pecb-video-player>

Properties:

| Property | Type | Default | Description | | ---------------------------------------------------------------------------------------------- | ---------------------------------- | ------------------------------ | ---------------------------------------------------------- | | src | string \| VideoPlayerSource[] | – | Media URL or list of <source> candidates | | poster | string | – | Poster image | | tracks | VideoPlayerTrack[] | [] | Caption/subtitle tracks (CC button appears when non-empty) | | title | string | – | Accessible title (part of the region label) | | crossOrigin | 'anonymous' \| 'use-credentials' | – | Needed for cross-origin caption files | | preload | 'none' \| 'metadata' \| 'auto' | 'metadata' | Preload hint | | autoplay / loop / playsInline | boolean | false / false / true | Native media flags | | startTime | number | 0 | Initial position in seconds | | watermark | string | 'PECB' | Bottom-left brand mark ('' hides it) | | label | string | – | Bottom-right label, e.g. SECTION 2 | | aspectRatio | string | '16 / 9' | CSS aspect ratio of the stage | | seekStep | number | 10 | Seconds for rewind button / arrow keys | | playbackRates | number[] | [0.5, 0.75, 1, 1.25, 1.5, 2] | Entries of the speed menu | | showCenterButton, showSkipBack, showVolume, showTime, showSettings, showFullscreen | boolean | true | Toggle individual controls | | showCaptions | boolean \| undefined | undefined | Force the CC button on/off (auto by default) | | labels | Partial<VideoPlayerLabels> | {} | Override user-visible strings (i18n) |

Two-way models ([(…)]): playing, currentTime, volume, muted, playbackRate, captionsEnabled.

Outputs: timeUpdate, loadedMetadata, seekEnd (VideoPlayerTimeEvent), playbackEnded, fullscreenChange (boolean), playerError (VideoPlayerError) — plus the …Change output of every model.

Public methods: play(), pause(), togglePlay(), seek(s), seekBy(Δs), skipBack(), toggleMute(), setVolume(v), setPlaybackRate(r), toggleCaptions(), toggleFullscreen().

Keyboard: Space/K play-pause · / or J/L seek ±seekStep · / volume · M mute · C captions · F fullscreen · Home/End · Esc closes the speed menu. The seek bar is a role="slider" with arrow/Page/Home/End support.

Theming (CSS custom properties on the host):

pecb-video-player {
  --pecb-video-radius: 12px; /* stage corner radius            */
  --pecb-video-bg: #0b0b0d; /* stage background               */
  --pecb-video-accent: #fff; /* progress fill, knob, slider    */
  --pecb-video-controls-color: rgba(255, 255, 255, 0.92);
  --pecb-video-font-display: "Plus Jakarta Sans", sans-serif;
  --pecb-video-font-body: "Inter", sans-serif;
  --pecb-video-focus-ring: #fff;
}

Course Content Panel Component

The Content sidebar from the Course Player — Content Redesign — the list that sits beside the course video. It reproduces the design exactly: Content / Transcript tabs, lesson search, the collapsible Section accordion (one section open at a time, auto-opening the section that owns the active lesson), status discs (completed · now playing · in-progress ring · not started · locked), quiz rows with badge and score, per-lesson progress bars, the connected "Part 1 / Part 2 …" rail for multi-part lessons, and a searchable transcript with click-to-seek, live cue highlighting and an autoscroll toggle.

It is standalone and data-driven — no player dependency — and sizes itself with container queries, so it works in a 408px sidebar, a drawer or a phone-width column.

import { CourseContentPanelComponent } from "@pecb-ui/components";
<pecb-course-content-panel [modules]="modules" [transcript]="cues" [(activeLessonId)]="lessonId" [playing]="playing()" [currentTime]="currentTime()" (lessonSelect)="openLesson($event)" (transcriptSeek)="player.seek($event)"> </pecb-course-content-panel>
const modules: CourseModule[] = [
  {
    id: "m2",
    title: "Introduction to management systems",
    lessons: [
      // A "Base — Part n" title makes the panel group consecutive lessons under one
      // sub-heading and list them as Part 1, Part 2, … on a connected rail.
      { id: "l2", title: "Introduction … — Part 1", durationSeconds: 1204, status: "completed" },
      { id: "l3", title: "Introduction … — Part 2", durationSeconds: 925, status: "completed" },
      { id: "l4", title: "Introduction … — Part 3", durationSeconds: 612, status: "playing", resumeSeconds: 440, src: "lesson-3.mp4", poster: "lesson-3.jpg", stageLabel: "SECTION 2" },
      { id: "q1", type: "quiz", title: "Introduction …", questionCount: 5, correctCount: 4, status: "completed" },
    ],
  },
];

Properties:

| Property | Type | Default | Description | | ------------------------------------------------------------------ | ----------------------------------- | --------------- | -------------------------------------------------------------------------------------- | | modules | CourseModule[] | [] | Course structure | | transcript | CourseTranscriptCue[] | [] | Fallback cues (a lesson's own transcript wins) | | playing | boolean | false | Live playback state — drives the "now playing" row | | currentTime | number | – | Live playhead: highlights the transcript cue and draws live progress on the active row | | layout | 'grouped' \| 'timeline' | 'grouped' | Section accordion, or one flat connected timeline | | density | 'comfortable' \| 'compact' | 'comfortable' | Row spacing | | accent | 'charcoal' \| 'red' | 'charcoal' | Colour of the "now playing" treatment | | showTabs, showSearch, showTranscriptSearch, showAutoscroll | boolean | true | Toggle chrome | | showProgress | boolean | false | Circular course-progress header + certificate pill | | showCertificate | boolean | true | The pill inside that header | | soloSingleLessonModules | boolean | false | Render one-lesson sections as direct-play rows | | labels | Partial<CourseContentPanelLabels> | {} | Override user-visible strings (i18n) |

Two-way models ([(…)]): activeLessonId, tab, openModuleId, autoscroll, query, transcriptQuery.

Outputs: lessonSelect (CourseLesson), transcriptSeek (seconds), moduleToggle ({ module, open }), certificateClick.

Lesson status: completed · playing · started · available · locked. Only the active lesson can render as "now playing", and a locked lesson is disabled and not selectable.

Accessibility: tabs are a real tablist (arrow keys switch), section headers are aria-expanded buttons with arrow/Home/End navigation, and collapsed sections are inert, so their lessons stay out of the tab order.


Course Player Component

The video stage with the Content sidebar attached — the full design composition, ready to drop in.

It wires pecb-video-player and pecb-course-content-panel together: picking a lesson loads its media and resumes where it left off, clicking a transcript cue seeks the video, and the sidebar follows playback live. Below ~900px of available width the two columns collapse into a stacked layout (container queries — it also works inside a drawer or split view).

import { CoursePlayerComponent } from "@pecb-ui/components";
<pecb-course-player [modules]="modules" [(activeLessonId)]="lessonId" [(playing)]="playing" [(currentTime)]="at" (lessonSelect)="track($event)" (lessonEnded)="markComplete($event)" (timeUpdate)="saveProgress($event.currentTime)"> </pecb-course-player>

Properties: modules, transcript, plus the video options src, poster, tracks, stageLabel (all used as fallbacks when the active lesson doesn't carry its own), watermark, aspectRatio, preload, crossOrigin, seekStep, videoLabels, autoPlayOnSelect, showLessonTitle; and the panel options panelPosition ('end' | 'start'), layout, density, accent, showTabs, showSearch, showProgress, soloSingleLessonModules, panelLabels.

Two-way models: activeLessonId, playing, currentTime, panelTab.

Outputs: lessonSelect, lessonEnded, timeUpdate, certificateClick, playerError.

Layout hooks:

pecb-course-player {
  --pecb-course-player-panel-width: 408px; /* side column width          */
  --pecb-course-player-gap: 20px; /* column gap                 */
  --pecb-course-player-panel-stacked-height: 460px; /* panel height once stacked  */
  --pecb-course-player-radius: 12px; /* panel corner radius        */
}

Side by side, the sidebar takes its height from the video stage and scrolls internally — give the player a height (or let the stage define it) and the columns stay aligned. Once stacked, the player grows to fit, so don't pin it to a fixed height in that range.

Theming the panel (both components):

pecb-course-content-panel {
  --pecb-course-panel-accent: #a11e29; /* "now playing" colour     */
  --pecb-course-panel-bg: #fff;
  --pecb-course-panel-border: #ececec;
  --pecb-course-panel-font-display: "Plus Jakarta Sans", sans-serif;
  --pecb-course-panel-font-body: "Inter", sans-serif;
}

Services

NotificationService

A stateless, injectable service for displaying toast notifications.

import { NotificationService } from '@pecb-ui/components';

@Component({...})
export class MyComponent {
  constructor(private notificationService: NotificationService) {}

  showSuccess(): void {
    this.notificationService.success('Operation completed successfully!');
  }

  showError(): void {
    this.notificationService.error('An error occurred', 'Error');
  }

  showWarning(): void {
    this.notificationService.warning('Please review your input', 'Warning');
  }

  showInfo(): void {
    this.notificationService.info('New updates available');
  }

  showCustom(): void {
    this.notificationService.show({
      message: 'Custom notification',
      type: 'info',
      duration: 10000,
      position: 'bottom-right',
      dismissible: true
    });
  }
}

ThemeService

A stateless, injectable service for managing application themes.

import { ThemeService } from '@pecb-ui/components';

@Component({...})
export class MyComponent {
  isDarkMode$ = this.themeService.theme$.pipe(
    map(config => config.resolvedTheme === 'dark')
  );

  constructor(private themeService: ThemeService) {}

  toggleTheme(): void {
    this.themeService.toggleTheme();
  }

  setDarkMode(): void {
    this.themeService.setTheme('dark');
  }

  useSystemPreference(): void {
    this.themeService.useSystemTheme();
  }

  setCustomColors(): void {
    this.themeService.setCustomVariables({
      primaryColor: '#007bff',
      backgroundColor: '#f8f9fa'
    });
  }
}

LoadingService

A stateless, injectable service for managing loading states.

import { LoadingService } from '@pecb-ui/components';

@Component({...})
export class MyComponent {
  isLoading$ = this.loadingService.isLoading$('data-fetch');

  constructor(private loadingService: LoadingService) {}

  async loadData(): Promise<void> {
    this.loadingService.start('data-fetch', 'Loading data...');
    try {
      const data = await this.fetchData();
      // Process data
    } finally {
      this.loadingService.stop('data-fetch');
    }
  }

  // Or use the helper method
  async loadDataAlt(): Promise<void> {
    const data = await this.loadingService.withLoading(
      'data-fetch',
      () => this.fetchData(),
      'Loading data...'
    );
  }
}

Utilities

The library exports utility functions for common operations:

import {
  // String utilities
  toKebabCase,
  toCamelCase,
  truncate,
  slugify,

  // DOM utilities
  generateUniqueId,
  getFocusableElements,
  trapFocus,
  copyToClipboard,

  // Accessibility utilities
  announceToScreenReader,
  getButtonAriaAttributes,
  prefersReducedMotion,

  // Error handling
  createError,
  wrapError,
  tryAsync,
} from "@pecb-ui/components";

Types & Interfaces

import {
  // Common types
  Size,
  ComponentSize,
  ColorVariant,
  Position,

  // Component interfaces
  Disableable,
  Loadable,
  Focusable,
  SelectableItem,
  MenuItem,

  // Service interfaces
  NotificationConfig,
  ThemeConfig,
  LoadingState,
} from "@pecb-ui/components";

SCSS Design System

Variables

// Colors
$primary-color: #1976d2;
$accent-color: #ff4081;
$success-color: #4caf50;
$warn-color: #f44336;
$info-color: #2196f3;

// Spacing (8px base unit)
$spacing-xs: 4px;
$spacing-sm: 8px;
$spacing-md: 16px;
$spacing-lg: 24px;
$spacing-xl: 32px;

// Typography
$font-family-base:
  "Inter",
  -apple-system,
  BlinkMacSystemFont,
  sans-serif;
$font-size-sm: 0.875rem;
$font-size-md: 1rem;
$font-size-lg: 1.125rem;

// Border radius
$border-radius-sm: 4px;
$border-radius-md: 8px;
$border-radius-lg: 12px;

Mixins

// Responsive breakpoints
@include respond-to("md") {
  // Styles for medium screens and up
}

// Flexbox utilities
@include flex-center;
@include flex-between;

// Elevation (shadows)
@include elevation(2);

// Focus visible
@include focus-visible {
  outline: 2px solid $primary-color;
}

// Transitions
@include transition(background-color, color);

Development

Prerequisites

  • Node.js 18+
  • npm 9+
  • Angular CLI 17.3+

Setup

# Clone the repository
git clone https://github.com/pecb-ui/components.git
cd components

# Install dependencies
npm install

# Build the library
npm run build

# Run Storybook
npm run storybook

# Run tests
npm test

Building

# Development build
npm run build

# Production build
npm run build:prod

# Create npm package
npm run publish:lib

Testing

# Run tests in watch mode
npm test

# Run tests once with coverage
npm run test:ci

Storybook

# Start Storybook dev server
npm run storybook

# Build Storybook for deployment
npm run build-storybook

Browser Support

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)

Roadmap

Planned components:

  • Input/Form controls
  • Modal/Dialog
  • Dropdown/Select
  • Tabs
  • Accordion
  • Table
  • Pagination
  • Badge
  • Avatar
  • Tooltip
  • Alert/Toast
  • Progress indicators
  • Date picker

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes using Conventional Commits
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT License - see the LICENSE file for details.

Support

For issues and questions, please use the GitHub issue tracker.