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

ryzen-ui

v0.2.18

Published

Ryzen UI — standalone Angular component library built with signals, OnPush, and pure CSS with oklch theming

Downloads

2,888

Readme

Ryzen UI

A lightweight, standalone Angular component library built with signals, OnPush change detection, pure component-scoped CSS with oklch color theming.

  • 27 components across Form, Data Display, Feedback, Navigation, and Overlay categories
  • Zero runtime dependencies beyond @angular/core and primeicons
  • No @angular/animations — CSS keyframes for all animations
  • No CDK — manual document listeners and position: fixed / absolute for overlays
  • Tree-shakeable, side-effect-free

Installation

pnpm add ryzen-ui primeicons
# or
npm install ryzen-ui primeicons

Optional peer dependencies (for Table export)

pnpm add exceljs jspdf html2canvas
# or
npm install exceljs jspdf html2canvas

Quick Start

import { Component } from '@angular/core';
import { ToastService } from 'ryzen-ui';
import { RzButtonComponent } from 'ryzen-ui';
// etc. — all components are standalone and tree-shakeable

Global styles

/* styles.css */
@import 'primeicons/primeicons.css';

:root {
  --color-primary: oklch(0.32 0.09 258);
  --color-surface: oklch(0.99 0 0);
  --color-border: oklch(0.83 0.015 260);
  --color-text: oklch(0.14 0.01 260);
  --color-text-muted: oklch(0.48 0.01 260);
  --color-surface-alt: oklch(0.975 0.005 260);
  --color-secondary: oklch(0.55 0.12 40);
  --color-accent: oklch(0.64 0.2 50);
  --color-success: oklch(0.55 0.18 145);
  --color-warning: oklch(0.68 0.18 75);
  --color-danger: oklch(0.55 0.22 25);
  --color-info: oklch(0.55 0.15 235);
  --radius-md: 0.5rem;
  --font-sans: ui-sans-serif, system-ui, sans-serif;
}

.dark {
  --color-primary: oklch(0.65 0.15 258);
  --color-surface: oklch(0.18 0.01 260);
  --color-border: oklch(0.3 0.01 260);
  --color-text: oklch(0.95 0.005 260);
  --color-text-muted: oklch(0.65 0.01 260);
  --color-surface-alt: oklch(0.22 0.01 260);
}

Theming

All components inherit colors through CSS custom properties. Each component's :host maps theme variables into internal --rz-* variables with oklch fallbacks, so components work without any theme setup:

| Variable | Default | Description | |----------|---------|-------------| | --rz-accent | oklch(0.32 0.09 258) | Accent/highlight color | | --rz-bg | oklch(0.99 0 0) | Input/component background | | --rz-border | oklch(0.83 0.015 260) | Border color | | --rz-text | oklch(0.14 0.01 260) | Text color | | --rz-muted | oklch(0.48 0.01 260) | Muted/secondary text | | --rz-radius | 0.5rem | Border radius |

Dark mode

Add .dark class to <html>:

<html class="dark">...</html>

The library uses @custom-variant dark (&:where(.dark, .dark *)) — no media query, fully manual toggle.


Components

Common Conventions

  • Size: 'sm' | 'md' | 'lg' — component size input, defaults to 'md'
  • Accent: applies via accentColor input (named ThemeColor or any raw CSS color string) or config.accentColor where available
  • Error/Hint: every form component supports error, errorMessage, and hint inputs
  • Animations: CSS @keyframes prefixed with rz-*, triggered by Angular animation triggers ([animate.enter], [animate.leave])
  • Panel positioning: panels (dropdown, datepicker) use position: fixed with manual (document:click) listener — no CDK

Form

<app-text-input>

Standard text input field.

| Input | Type | Default | |-------|------|---------| | value | model<string> | '' | | placeholder | string | '' | | size | FieldSize | 'md' | | disabled | boolean | false | | accentColor | string \| null | null | | hint | string | '' | | errorMessage | string | '' | | error | boolean | false | | width | string | '' |

<app-text-input [(value)]="name" placeholder="Enter name" [accentColor]="'primary'" />

<app-password-input>

Password field with visibility toggle.

| Input | Type | Default | |-------|------|---------| | value | model<string> | '' | | placeholder | string | '' | | size | FieldSize | 'md' | | disabled | boolean | false | | accentColor | string \| null | null | | hint | string | '' | | errorMessage | string | '' | | error | boolean | false | | hideToggle | boolean | false | | width | string | '' |

Methods: toggleVisibility()

<app-password-input [(value)]="password" placeholder="Password" />

<app-search-input>

Search field with search icon and clear button.

| Input | Type | Default | |-------|------|---------| | value | model<string> | '' | | placeholder | string | 'Search...' | | size | FieldSize | 'md' | | disabled | boolean | false | | accentColor | string \| null | null | | hint | string | '' | | errorMessage | string | '' | | error | boolean | false | | width | string | '' |

<app-search-input [(value)]="query" placeholder="Search users..." />

<app-checkbox>

Checkbox with optional label, indeterminate state.

| Input | Type | Default | |-------|------|---------| | checked | model<boolean> | false | | label | string | '' | | size | FieldSize | 'md' | | disabled | boolean | false | | accentColor | string \| null | null | | indeterminate | boolean | false | | hint | string | '' | | errorMessage | string | '' | | error | boolean | false | | width | string | '' |

Methods: toggle()

<app-checkbox [(checked)]="agreed" label="I agree to the terms" />
<app-checkbox [indeterminate]="true" label="Select all" />

<app-toggle>

Switch/toggle control.

| Input | Type | Default | |-------|------|---------| | checked | model<boolean> | false | | label | string | '' | | size | FieldSize | 'md' | | disabled | boolean | false | | accentColor | string \| null | null | | hint | string | '' | | errorMessage | string | '' | | error | boolean | false | | width | string | '' |

Methods: toggle()

<app-toggle [(checked)]="notifications" label="Enable notifications" />

<app-date-picker>

Inline calendar with month/year navigation, today button.

| Input | Type | Default | |-------|------|---------| | value | model<Date \| null> | null | | placeholder | string | 'Select date' | | size | FieldSize | 'md' | | disabled | boolean | false | | accentColor | string \| null | null | | hint | string | '' | | errorMessage | string | '' | | error | boolean | false | | format | 'dd/MM/yyyy' \| 'MM/dd/yyyy' \| 'yyyy-MM-dd' | 'dd/MM/yyyy' | | width | string | '' | | config | DatePickerConfig | {} |

DatePickerConfig:

interface DatePickerConfig {
  minDate?: Date;
  maxDate?: Date;
  disabledDates?: Date[];
  highlightedDates?: Date[];
  firstDayOfWeek?: 0 | 1;       // 0=Sunday, 1=Monday
  showTodayButton?: boolean;     // default true
  showYearNavigation?: boolean;  // default true
  appendToParent?: boolean;      // default false
}
<app-date-picker [(value)]="dob" placeholder="Date of birth" />
<app-date-picker [(value)]="range" [config]="{ minDate: today, firstDayOfWeek: 1 }" />

<app-drop-down>

Searchable dropdown with smart panel positioning.

| Input | Type | Default | |-------|------|---------| | value | model<string \| null> | null | | options | (DropdownOption \| string)[] | [] | | placeholder | string | 'Select...' | | size | FieldSize | 'md' | | disabled | boolean | false | | accentColor | string \| null | null | | searchable | boolean | false | | hint | string | '' | | errorMessage | string | '' | | error | boolean | false | | width | string | '' | | config | DropdownConfig | {} |

DropdownOption<T>:

interface DropdownOption<T = unknown> {
  label: string;
  value: T;
}

DropdownConfig:

interface DropdownConfig {
  appendToParent?: boolean;
  maxHeight?: string;
  direction?: 'up' | 'down' | 'auto';
}
<app-drop-down [(value)]="country" :options="['Egypt', 'UAE', 'KSA']" />
<app-drop-down
  [(value)]="user"
  [options]="[{ label: 'Alice', value: 1 }, { label: 'Bob', value: 2 }]"
  searchable
/>

<app-multi-select>

Multi-select with chips, search, select all.

| Input | Type | Default | |-------|------|---------| | value | model<string[]> | [] | | options | (DropdownOption \| string)[] | [] | | placeholder | string | 'Select...' | | size | FieldSize | 'md' | | disabled | boolean | false | | selectAll | boolean | true | | selectAllLabel | string | 'Select All' | | accentColor | string \| null | null | | searchable | boolean | false | | hint | string | '' | | errorMessage | string | '' | | error | boolean | false | | width | string | '' | | config | MultiSelectConfig | {} |

MultiSelectConfig:

interface MultiSelectConfig {
  appendToParent?: boolean;
  maxHeight?: string;
  direction?: 'up' | 'down' | 'auto';
}
<app-multi-select
  [(value)]="roles"
  [options]="['Admin', 'Editor', 'Viewer']"
  searchable
/>

<app-file-upload>

Drag-and-drop file upload with validation.

| Input | Type | Default | |-------|------|---------| | files | model<File[]> | [] | | accept | string | '' | | multiple | boolean | true | | maxSize | number (bytes) | 0 (unlimited) | | maxFiles | number | 0 (unlimited) | | disabled | boolean | false | | placeholder | string | 'Drop files here or click to browse' | | size | FieldSize | 'md' | | hint | string | '' | | errorMessage | string | '' | | error | boolean | false |

<app-file-upload [(files)]="docs" accept=".pdf,.docx" [maxSize]="5 * 1024 * 1024" />

Data Display

<app-badge>

Small label/badge with variants.

| Input | Type | Default | |-------|------|---------| | accentColor | string | 'primary' | | variant | 'filled' \| 'outlined' \| 'subtle' | 'filled' | | size | FieldSize | 'sm' | | dot | boolean | false |

<app-badge variant="subtle" accentColor="success">Active</app-badge>
<app-badge [dot]="true" accentColor="danger" />

<app-skeleton>

Placeholder loader.

| Input | Type | Default | |-------|------|---------| | variant | 'text' \| 'circle' \| 'rect' | 'text' | | width | string | '' | | height | string | '' | | count | number | 1 | | borderRadius | string | '' | | accentColor | string | '' |

<app-skeleton variant="circle" width="40px" height="40px" />
<app-skeleton variant="text" count="3" />

<app-table-skeleton>

Table skeleton loader.

| Input | Type | Default | |-------|------|---------| | rows | number | 5 | | columns | number \| TableSkeletonColumn[] | 4 |

<app-table-skeleton [rows]="8" [columns]="6" />

<app-tooltip>

Overflow-detection tooltip.

| Input | Type | Default | |-------|------|---------| | text | string | '' | | position | 'top' \| 'bottom' \| 'left' \| 'right' | 'top' | | disabled | boolean | false | | alwaysShow | boolean | false |

<app-tooltip text="This is a tooltip" position="top">
  <span>Hover me</span>
</app-tooltip>

<app-table>

Full-featured data table with sorting, column filtering, text search, pagination, row selection, column resize, column toggle, Excel/PDF export, row expansion, sticky header, and skeleton loading.

| Input | Type | Default | Description | |-------|------|---------|-------------| | columns | ColumnDef[] | [] | Column definitions | | rows | Record<string, unknown>[] | [] | Data rows | | striped | boolean | false | Alternating row colors | | hoverable | boolean | true | Highlight row on hover | | sortable | boolean | false | Enable column sorting | | emptyMessage | string | 'No data available' | Text when no rows match | | width | string | '' | CSS width | | loading | boolean | false | Show skeleton loader | | searchable | boolean | false | Global text search bar | | filterable | boolean | false | Per-column filter dropdowns | | skeletonRows | number | 5 | Skeleton row count during loading | | resizable | boolean | false | Drag column borders to resize | | pageable | boolean | false | Pagination controls | | pageSize | number | 10 | Rows per page | | pageSizeOptions | number[] | [5, 10, 20, 50] | Page size choices | | selectable | boolean | false | Checkbox row selection | | selectionMode | 'single' \| 'multiple' | 'multiple' | Single or multi select | | selectionKey | string | '' | Row property for selection identity | | stickyHeader | boolean | false | Fixed header on scroll | | exportable | boolean | false | Excel (xlsx) + PDF export buttons | | columnToggle | boolean | false | Show/hide columns menu | | expandable | boolean | false | Expandable row detail |

| Output | Emit Type | Description | |--------|-----------|-------------| | sortChange | SortChange | Emitted when sort column/direction changes | | pageChange | number | Emitted on page navigation | | selectedRows | Record<string, unknown>[] | Emitted when selection changes |

interface SortChange {
  key: string;
  direction: 'asc' | 'desc' | '';
}

ColumnDef:

interface ColumnDef {
  key: string;                                    // Property name in row data
  header: string;                                 // Display header text
  sortable?: boolean;                             // Allow sorting on this column
  width?: string;                                 // CSS width (e.g. '120px', '2fr')
  align?: 'left' | 'center' | 'right';           // Text alignment
  cell?: (row: Record<string, unknown>) => string; // Custom cell renderer
  rowSpan?: (row, index, rows) => number;         // Dynamic rowspan (0=hidden, >1=span)
  actions?: ActionDef[];                           // Action buttons per row
  filterable?: boolean;                            // Show filter dropdown
  filterOptions?: { label: string; value: string }[]; // Filter choices
  footer?: (rows: Record<string, unknown>[]) => string; // Footer text aggregator
  truncate?: boolean;                              // Truncate long text
  maxChars?: number;                               // Max chars before truncation
}

ActionDef:

interface ActionDef {
  icon?: string;                                    // PrimeIcon class (e.g. 'pi pi-pencil')
  label?: string;                                   // Button text (falls back to icon-only)
  accentColor?: string;                             // Button color
  visible?: (row: Record<string, unknown>) => boolean; // Conditional visibility
  disabled?: (row: Record<string, unknown>) => boolean; // Conditional disable
  click: (row: Record<string, unknown>) => void;    // Click handler
}

Complete usage example:

import { Component } from '@angular/core';
import { ColumnDef, SortChange } from 'ryzen-ui';

@Component({ ... })
export class MyComponent {
  cols: ColumnDef[] = [
    { key: 'name', header: 'Name', sortable: true, width: '150px' },
    { key: 'email', header: 'Email', sortable: true, filterable: true, filterOptions: [] },
    { key: 'role', header: 'Role', sortable: true },
    {
      key: 'actions',
      header: '',
      width: '100px',
      align: 'center',
      actions: [
        {
          icon: 'pi pi-pencil',
          accentColor: 'primary',
          click: row => this.edit(row),
        },
        {
          icon: 'pi pi-trash',
          accentColor: 'danger',
          visible: row => row['status'] !== 'archived',
          click: row => this.delete(row),
        },
      ],
    },
  ];

  data = [
    { name: 'Alice', email: '[email protected]', role: 'Admin', status: 'active' },
    { name: 'Bob', email: '[email protected]', role: 'User', status: 'active' },
  ];

  onSort(ev: SortChange) { console.log('sort', ev); }
  onPage(page: number) { console.log('page', page); }
  onSelect(rows: Record<string, unknown>[]) { console.log('selected', rows); }
  edit(row: any) { /* ... */ }
  delete(row: any) { /* ... */ }
}
<app-table
  [columns]="cols"
  [rows]="data"
  sortable
  pageable
  searchable
  filterable
  selectable
  selectionKey="email"
  exportable
  columnToggle
  resizable
  striped
  stickyHeader
  (sortChange)="onSort($event)"
  (pageChange)="onPage($event)"
  (selectedRows)="onSelect($event)"
/>

<!-- With expandable rows -->
<app-table [columns]="cols" [rows]="data" expandable>
  <ng-template #rowDetail let-row>
    <div style="padding: 1rem">
      <p><strong>Email:</strong> {{ row['email'] }}</p>
      <p><strong>Role:</strong> {{ row['role'] }}</p>
    </div>
  </ng-template>
</app-table>

<app-image-upload>

Image uploader with thumbnail previews.

| Input | Type | Default | |-------|------|---------| | images | model<ImageFile[]> | [] | | multiple | boolean | true | | maxFiles | number | 4 | | maxSize | number (bytes) | 2097152 (2 MB) | | disabled | boolean | false | | size | FieldSize | 'md' | | placeholder | string | 'Drop images or click' | | errorMessage | string | '' | | error | boolean | false |

interface ImageFile {
  file: File;
  url: string;
}
<app-image-upload [(images)]="photos" [maxFiles]="6" />

<app-carousel>

Image carousel with autoplay, dots, arrows.

| Input | Type | Default | |-------|------|---------| | items | CarouselItem[] | required | | height | string | '320px' | | autoPlay | boolean | true | | interval | number (ms) | 4000 | | showArrows | boolean | true | | showDots | boolean | true |

interface CarouselItem {
  src: string;
  alt?: string;
  title?: string;
  description?: string;
}
<app-carousel [items]="slides" [autoPlay]="false" />

<app-banner-slider>

Hero banner with CTA button and text overlays.

| Input | Type | Default | |-------|------|---------| | items | BannerItem[] | required | | height | string | '400px' | | autoPlay | boolean | true | | interval | number (ms) | 5000 | | showArrows | boolean | true | | showDots | boolean | true | | overlayMode | 'left' \| 'center' \| 'right' | 'left' |

| Output | Emit Type | |--------|-----------| | ctaClick | BannerItem |

interface BannerItem {
  src: string;
  alt?: string;
  title?: string;
  subtitle?: string;
  description?: string;
  ctaLabel?: string;
  ctaLink?: string;
}
<app-banner-slider [items]="banners" (ctaClick)="onCtaClick($event)" />

<app-empty-state>

Centered placeholder with icon and action.

| Input | Type | Default | |-------|------|---------| | icon | string | '' | | title | string | '' | | message | string | '' | | width | string | '' |

<app-empty-state icon="pi pi-inbox" title="No messages" message="You have no unread messages" />

Feedback

<app-alert>

Colored alert banner with icon and dismiss.

| Input | Type | Default | |-------|------|---------| | type | 'success' \| 'warning' \| 'danger' \| 'info' | 'info' | | title | string | '' | | dismissible | boolean | true | | showIcon | boolean | true | | width | string | '' |

| Output | Emit Type | |--------|-----------| | dismiss | void |

<app-alert type="success" title="Operation completed successfully" (dismiss)="onDismiss()" />
<app-alert type="danger" [dismissible]="false" title="Critical error" />

<app-spinner>

Loading spinner with 5 variants, image/text support, overlay mode.

| Input | Type | Default | |-------|------|---------| | variant | 'circle' \| 'dual-ring' \| 'dots' \| 'pulse' \| 'bars' | 'dual-ring' | | size | FieldSize | 'md' | | accentColor | string | 'primary' | | overlay | boolean | false | | text | string | '' | | imageUrl | string | '' |

<app-spinner variant="dots" text="Loading..." />
<app-spinner [overlay]="true" variant="bars" />
<app-spinner variant="circle" [imageUrl]="'/assets/logo.png'" />

<app-toast>

Toast notification container (used with ToastService).

| Input | Type | Default | |-------|------|---------| | position | 'top-right' \| 'top-left' \| 'bottom-right' \| 'bottom-left' | 'top-right' | | maxVisible | number | 5 |

<app-toast position="top-right" />

ToastService (providedIn: 'root'):

class ToastService {
  readonly toasts: Signal<ToastMessage[]>;

  show(type: ToastType, message: string, config?: { title?: string; duration?: number }): void;
  success(message: string, config?: { title?: string; duration?: number }): void;
  error(message: string, config?: { title?: string; duration?: number }): void;
  warning(message: string, config?: { title?: string; duration?: number }): void;
  info(message: string, config?: { title?: string; duration?: number }): void;
  remove(id: number): void;
  clear(): void;
}
@Component({ ... })
export class MyComponent {
  private toast = inject(ToastService);

  save() {
    this.toast.success('Data saved', { title: 'Success', duration: 3000 });
  }
}

Navigation

<app-accordion> + <app-accordion-panel>

Collapsible accordion with single/multi mode.

<app-accordion>:

| Input | Type | Default | |-------|------|---------| | multi | boolean | false |

<app-accordion-panel>:

| Input | Type | Default | |-------|------|---------| | expanded | model<boolean> | false | | title | string | '' | | disabled | boolean | false |

<app-accordion [multi]="true">
  <app-accordion-panel title="Section 1" [(expanded)]="sec1">
    Content for section 1
  </app-accordion-panel>
  <app-accordion-panel title="Section 2" [disabled]="true">
    Disabled content
  </app-accordion-panel>
</app-accordion>

<app-sidebar>

Responsive sidebar with collapse, submenus, logout.

| Input | Type | Default | |-------|------|---------| | isOpen | boolean | false | | isCollapsed | boolean | false | | items | SidebarItem[] | [] | | title | string | 'Menu' | | width | string | '280px' | | activeId | string \| null | null |

| Output | Emit Type | |--------|-----------| | toggleOpen | void | | toggleCollapse | void | | itemClick | SidebarItem | | logout | void |

interface SidebarItem {
  id: string;
  label: string;
  icon?: string;
  route?: string;
  children?: SidebarItem[];
}
<app-sidebar
  [isOpen]="sidebarOpen"
  [isCollapsed]="sidebarCollapsed"
  [items]="menuItems"
  [activeId]="currentRoute"
  (itemClick)="onNav($event)"
  (toggleCollapse)="sidebarCollapsed = !sidebarCollapsed"
  (logout)="onLogout()"
/>

<app-nav>

Top navbar with desktop dropdowns, mobile hamburger menu.

| Input | Type | Default | |-------|------|---------| | items | NavItem[] | [] | | brandText | string | 'Brand' | | brandIcon | string | 'pi pi-box' | | fixed | boolean | true |

| Output | Emit Type | |--------|-----------| | logout | void |

interface NavItem {
  id: string;
  label: string;
  icon?: string;
  route?: string;
  children?: NavItem[];
}
<app-nav
  [items]="navItems"
  brandText="MyApp"
  brandIcon="pi pi-cog"
  (logout)="onLogout()"
/>

Overlay

<app-modal>

Modal dialog with title slot, footer slot, close on backdrop/escape.

| Input | Type | Default | |-------|------|---------| | open | model<boolean> | false | | title | string | '' | | width | string | '' | | config | ModalConfig | {} |

| Output | Emit Type | |--------|-----------| | closed | void |

Methods: close()

interface ModalConfig {
  closeOnBackdrop?: boolean;   // default true
  closeOnEscape?: boolean;     // default true
  showCloseButton?: boolean;   // default true
  maxWidth?: string;           // default '500px'
}
<app-modal [(open)]="modalOpen" title="Edit User" [config]="{ maxWidth: '600px' }">
  <p>Modal body content</p>
  <ng-template #modalFooter>
    <button (click)="save()">Save</button>
  </ng-template>
</app-modal>

<app-confirm-dialog>

Pre-built confirmation dialog with customizable icon, accent, and button text.

| Input | Type | Default | |-------|------|---------| | open | model<boolean> | false | | message | string | '' | | config | ConfirmDialogConfig | {} |

| Output | Emit Type | |--------|-----------| | confirm | void | | cancel | void |

interface ConfirmDialogConfig {
  title?: string;
  confirmText?: string;
  cancelText?: string;
  icon?: string;
  confirmAccent?: ThemeColor | string;
  closeOnBackdrop?: boolean;
  closeOnEscape?: boolean;
}
<app-confirm-dialog
  [(open)]="confirmOpen"
  message="Are you sure you want to delete this item?"
  [config]="{ title: 'Confirm Delete', confirmAccent: 'danger', confirmText: 'Delete' }"
  (confirm)="deleteItem()"
  (cancel)="confirmOpen = false"
/>

Shared Types

type FieldSize = 'sm' | 'md' | 'lg';
type ThemeColor = 'primary' | 'secondary' | 'accent' | 'success' | 'warning' | 'danger' | 'info';

Development

# Build the library
npm run build

# Watch mode
npm run watch

# Publish
npm run publish

Build artifacts go to dist/rz-lib/.


Conventions

| Convention | Value | |------------|-------| | Change detection | ChangeDetectionStrategy.OnPush | | State management | Angular signal() / model() / computed() / input() | | Styling | Pure CSS custom properties — no @angular/animations | | Theming | CSS variables with oklch fallbacks (light + dark) | | Positioning | CSS position: fixed / absolute — no CDK overlay | | Animation | @keyframes + setTimeout for close sequence | | Panel pattern | Open: setTimeout(0) → enter animation. Close: _isClosing signal → wait 150ms → remove | | document:click | Manually bound/unbound on open/close | | Timer cleanup | _closeTimer field + ngOnDestroy | | Field sizes | sm / md / lg | | Accent colors | Named theme colors or any raw CSS color string |


Dependencies

| Dependency | Type | Required | |------------|------|----------| | @angular/core | peer | Yes | | @angular/common | peer | Yes | | primeicons | peer | Yes | | exceljs | optional peer | Table export (XLSX) | | jspdf | optional peer | Table export (PDF) | | html2canvas | optional peer | Table export (PDF) |


License

MIT