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

@sixbell-telco/sdk

v3.1.0

Published

A collection of reusable components designed for use in Sixbell Telco Angular projects

Downloads

367

Readme

Sixbell Telco SDK - Component Library

Welcome to the Sixbell Telco SDK component library. This is a comprehensive Angular component library featuring 25+ pre-built, production-ready UI components built with Angular 19, Tailwind CSS v4, and DaisyUI.

The package is public on npm, but the repository is private and there is no separate public docs page. Because of that, this README keeps setup guidance, provider examples, translation examples, and usage references that public consumers need.

Overview

The SDK provides:

  • 25+ Production-Ready Components: Button, Card, Form inputs, Data Table, Dialog, Dropdown, and more
  • Utility Services: Theme management, i18n translation, logging, runtime config, and formatting
  • Directive Support: Text, auto-focus, click-outside, and only-numbers
  • Tailwind CSS v4 + DaisyUI: Modern styling with customizable themes
  • Multi-Entry Point Architecture: Granular imports for optimized bundle sizes
  • Angular 19 Signal-Based: Modern reactive patterns with Angular signals

Pre-requirements

  • Node.js: ^18.19.1 || ^20.11.1 || ^22.0.0
  • npm: 9.6.7 || ^10.0.0 || ^11.0.0
  • Angular: 19.2.x or later
  • Tailwind CSS: v4.x

Available Components

General Components

| Component | Description | | ------------- | ----------------------------------------------------------------------------- | | Accordion | Expandable/collapsible content sections | | Avatar | User profile pictures with initials fallback | | Badge | Status and label indicators | | Button | Interactive buttons with variants and sizes | | Card | Compound content shell with contextual headers, sections, and divided footers | | Countdown | Countdown timer with customizable formatting | | Data Table | Advanced grid with sorting, filtering, pagination, row actions | | Dialog | Modal dialogs with animations and positioning | | Divider | Semantic divider with orientation, placement, and margin control | | Dropdown | Menu dropdown for actions and links | | Dropdown Menu | Advanced nested menu system | | Dual List | Dual list selection for managing items | | File Upload | File uploader and dropzone entry point | | Icon | SVG icon component with ng-icons integration | | Link | Hyperlink component with router support | | Notification | Toast-style notifications | | Overlay | Generic overlay with trigger and content | | Paginator | Pagination controls for data | | Product Card | Product display card with image and actions | | Progress | Progress bar with variants | | Sonner Toast | Modern toast notifications via ngx-sonner | | Tab | Tabbed navigation interface | | Table | Data table with inline and dropdown row actions | | Text | Polymorphic text component with semantic HTML tags | | Tooltip | Floating tooltip with positioning | | Wizard | Multi-step wizard with validation |

Form Components

| Component | Description | | ---------- | ----------------------------------------------- | | Checkbox | Checkbox input with multiple variants | | Combobox | Searchable dropdown with multi-select support | | Datepicker | Single and range date selection with time | | Form Item | Layout wrapper for fields + full-width errors | | Form Error | Error message display for form fields | | Form Group | Vertical spacing helper for grouped fields | | Input | Text input with variants, icons, and validation | | Radio | Radio button group selection | | Range | Slider input for numeric ranges | | Select | Dropdown select with object support | | Switch | Toggle switch for boolean values | | Textarea | Multi-line text input with character count | | Toggle | Toggle button for boolean states |

Form Errors Layout

For full-width validation messages, wrap each field and its error with st-form-item and place st-form-errors as a sibling (not inside the field component).

<st-form-item>
  <st-input formControlName="controlInput" label="Input" [parentForm]="form"></st-input>
  <st-form-errors [parentForm]="form" formField="controlInput" />
</st-form-item>

Migration notes: projects/sdk/components/forms/form-error/MIGRATION.md

Optional Label Indicator

Form fields can display an optional indicator inline with the legend. Enable it with showOptional and customize the text with optionalText.

<st-input label="Email" [showOptional]="true" [optionalText]="'sdk.formFields.optional'"></st-input>

Add the translation key to your SDK translations (example shown for English):

{
  "sdk": {
    "formFields": {
      "optional": "Optional"
    }
  }
}

Utility Services

| Service | Purpose | Documentation | | ------------------- | -------------------------------------------------- | ------------------------------- | | Logger Service | Theme-aware logging with IndexedDB persistence | README | | Theme Service | Runtime theme management with multi-tenant support | README | | Translation Service | Multi-language i18n with ngx-translate integration | README | | Formatting Service | Reactive locale-aware formatting utilities | README | | Runtime Config | Last-known-good runtime config loader | README |

Directives

| Directive | Purpose | | ------------- | ----------------------------------------------------- | | auto-focus | Auto-focus elements when visible | | click-outside | Detect clicks outside an element | | only-numbers | Restrict input to numeric values only | | text | Apply preset-based typography with line-clamp support |

Installation

Step 1: Install Dependencies

npm install @sixbell-telco/sdk @tailwindcss/typography daisyui @midudev/tailwind-animations

Step 2: Import SDK Styles

In your global styles.css:

@import '../node_modules/@sixbell-telco/sdk/_index.css';

@import 'tailwindcss';

@source '../src';

⚠️ Important: Import _index.css before the tailwind import.

Step 3: Configure Microfrontend (if applicable)

Add path mapping to tsconfig.json:

{
  "compilerOptions": {
    "paths": {
      "@sixbell-telco/sdk/*": ["./node_modules/@sixbell-telco/sdk/*"]
    }
  }
}

Step 4: Optional - VSCode Configuration

Install the Tailwind CSS extension and add to .vscode/settings.json:

{
  "editor.quickSuggestions": {
    "strings": "on"
  },
  "files.associations": {
    "*.css": "tailwindcss"
  },
  "tailwindCSS.classFunctions": ["tw", "clsx", "cva", "cn", "tw\\.[a-z-]+"]
}

These settings improve your development experience:

  • editor.quickSuggestions: Enables autosuggestions within string literals
  • files.associations: Ensures CSS files are treated as Tailwind CSS files
  • tailwindCSS.classFunctions: Enables Tailwind intellisense in utility functions like tw(), clsx(), and cva(), etc that are used for conditional class name composition

With these settings, VSCode will provide class autocompletion for the library's custom attributes and utility functions.

Themes and translation configuration (an optional Logger)

Configure all services using runtime providers in your app.config.ts. This approach loads configuration from JSON files at runtime, eliminating the need to recompile when updating themes or languages.

Logger Service Setup

Configure logging with different levels for development and production:

import { provideLogger } from '@sixbell-telco/sdk/utils/logger';

export const appConfig: ApplicationConfig = {
  providers: [
    provideLogger({
      enableLogging: true,
      logLevel: isDevMode() ? 'debug' : 'warn', // trace | debug | info | warn | error | fatal
      showTimestamp: true,
      persistToIndexedDB: true, // Persistent storage for debugging
      silentMode: !isDevMode(),
    }),
  ],
};

Translation Service Setup

The Translation Service loads language configuration from a runtime JSON file:

import { provideRuntimeTranslation } from '@sixbell-telco/sdk/utils/translation';

export const appConfig: ApplicationConfig = {
  providers: [provideRuntimeTranslation('/assets/translation/translation.json')],
};

The translation.json file defines available languages and their translation paths:

{
  "meta": {
    "updatedAt": "2026-02-02T00:00:00Z",
    "hash": "showcase-translations-v2",
    "schemaVersion": "3"
  },
  "additionalLanguages": [],
  "defaultLang": "es",
  "excludeLanguages": [],
  "translationPaths": ["/assets/i18n/", "/assets/i18n/sdk/", "/assets/themes/i18n"],
  "locale": {
    "defaultLocale": "en-US",
    "languageToLocale": {
      "en": "en-US",
      "es": "es-CL",
      "pt": "pt-BR"
    }
  }
}

Theme Service Setup

The Theme Service loads theme configuration from a runtime JSON file:

import { provideRuntimeTheme } from '@sixbell-telco/sdk/utils/theme';

export const appConfig: ApplicationConfig = {
  providers: [provideRuntimeTheme('/assets/themes/themes.json')],
};

The themes.json file defines available themes and their variants:

{
  "meta": {
    "updatedAt": "2026-02-02T00:00:00Z",
    "hash": "showcase-themes-v1",
    "schemaVersion": "2"
  },
  "themes": ["/assets/themes/catalog/sixbell_telco.json", "/assets/themes/catalog/wom.json"],
  "defaultTheme": "sixbell_telco",
  "defaultScheme": "system",
  "excludeThemes": []
}

Complete Configuration Example

// app.config.ts
import { ApplicationConfig, isDevMode } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { provideLogger } from '@sixbell-telco/sdk/utils/logger';
import { provideRuntimeTheme } from '@sixbell-telco/sdk/utils/theme';
import { provideRuntimeTranslation } from '@sixbell-telco/sdk/utils/translation';
import { from, timer } from 'rxjs';
import { switchMap } from 'rxjs/operators';

const runtimeUpdateStream$ = timer(0, environment.runtime.pollIntervalMs).pipe(
  switchMap(() => from(fetch(`${environment.runtime.apiBaseUrl}/api/runtime/updates/poll`))),
  switchMap((response) => from(response.json())),
);

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(),
    provideLogger({
      enableLogging: isDevMode(),
      logLevel: isDevMode() ? 'debug' : 'warn',
      showTimestamp: true,
      showDate: true,
      persistToIndexedDB: true,
      idbRetentionDays: 7,
      idbMaxLogs: 5000,
      silentMode: !isDevMode(),
    }),
    ...(() => {
      const mode = environment.runtime.defaultMode;

      if (mode === 'default') {
        return [provideRuntimeTranslation('/assets/translation/translation.json'), provideRuntimeTheme('/assets/themes/themes.json')];
      }

      if (mode === 'stream' && environment.runtime.enableStream) {
        return [
          provideRuntimeTranslation(`${environment.runtime.apiBaseUrl}/api/runtime/translation/config`, {
            updateStream: runtimeUpdateStream$,
          }),
          provideRuntimeTheme(`${environment.runtime.apiBaseUrl}/api/runtime/theme/config`, {
            updateStream: runtimeUpdateStream$,
          }),
        ];
      }

      return [
        provideRuntimeTranslation(`${environment.runtime.apiBaseUrl}/api/runtime/translation/config`, {
          sse: {
            url: `${environment.runtime.apiBaseUrl}/api/runtime/updates?resource=translation`,
            eventType: 'message',
          },
        }),
        provideRuntimeTheme(`${environment.runtime.apiBaseUrl}/api/runtime/theme/config`, {
          sse: {
            url: `${environment.runtime.apiBaseUrl}/api/runtime/updates?resource=theme`,
            eventType: 'message',
          },
        }),
      ];
    })(),
  ],
};

Reactive Formatting Setup

Use SDK formatting pipes and services instead of Angular native locale pipes:

{{ createdAt | stDate:'DD/MM/YYYY HH:mm:ss' }} {{ total | stDecimal:'1.0-2' }} {{ amount | stCurrency:'CLP':'symbol':'1.0-0' }} {{ ratio |
stPercent:'1.0-2' }}
import { Component, computed, inject } from '@angular/core';
import { FormattingService } from '@sixbell-telco/sdk/utils/formatting';

@Component({
  selector: 'app-formatting-example',
  template: `{{ formattedAmount() }}`,
})
export class FormattingExampleComponent {
  private readonly formatting = inject(FormattingService);

  amount = 1234.5;

  formattedAmount = computed(() =>
    this.formatting.formatCurrency(this.amount, 'CLP', {
      display: 'symbol',
      digitsInfo: '1.0-0',
    }),
  );
}

Key Benefits of Runtime Configuration:

  • No Recompilation: Update themes and languages without rebuilding your application
  • Multi-Tenant Support: Easily switch themes and language sets per deployment environment
  • Caching Strategy: RuntimeConfigStore keeps last-known-good config in Dexie
  • Hot Updates: Changes to themes.json and translation.json take effect immediately
  • Persistent Preferences: User language and theme preferences are stored and restored automatically

Adding Custom Themes

Themes are managed through a runtime configuration file (themes.json) with theme JSON files. This approach allows for:

  • Zero Recompilation: Add/modify themes without rebuilding
  • Multi-Tenant Support: Different themes per deployment
  • Dynamic Assets: Brand-specific logos and images per theme
  • Runtime Fonts: Load custom fonts for each theme
  • Hot Updates: Changes take effect immediately

1. Update themes.json

Add your custom theme to /assets/themes/themes.json:

{
  "meta": {
    "updatedAt": "2026-02-02T00:00:00Z",
    "hash": "showcase-themes-v1",
    "schemaVersion": "2"
  },
  "themes": ["/assets/themes/catalog/sixbell_telco.json", "/assets/themes/catalog/wom.json"],
  "defaultTheme": "sixbell_telco",
  "defaultScheme": "system",
  "excludeThemes": []
}

Configuration Structure:

  • themes: theme JSON paths
  • defaultTheme: initial theme on app startup
  • defaultScheme: initial color scheme ('system', 'light', or 'dark')

2. Define DaisyUI Themes in Global Styles

In your global styles.css, import the SDK and define your theme CSS:

@import '../node_modules/@sixbell-telco/sdk/_index.css';

@import 'tailwindcss';

@source '../src';

3. Use Themes in Components

The ThemeService automatically loads available themes from themes.json:

import { Component, ChangeDetectionStrategy, inject, computed } from '@angular/core';
import { ThemeService, ThemeAssetsService } from '@sixbell-telco/sdk/utils/theme';
import { ButtonComponent } from '@sixbell-telco/sdk/components/button';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-theme-switcher',
  standalone: true,
  imports: [CommonModule, ButtonComponent],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div>
      <select (change)="themeService.setTheme($event.target.value)">
        @for (theme of themeService.getAvailableThemes(); track theme) {
          <option [value]="theme" [selected]="themeService.selectedTheme() === theme">
            {{ themeService.getLocalizedThemeName(theme) }}
          </option>
        }
      </select>

      <select (change)="themeService.setScheme($event.target.value)">
        @for (scheme of themeService.getAvailableSchemes(); track scheme) {
          <option [value]="scheme" [selected]="themeService.selectedScheme() === scheme">
            {{ themeService.getLocalizedSchemeName(scheme) }}
          </option>
        }
      </select>

      <img [src]="logoUrl()" alt="Brand Logo" />
    </div>
  `,
})
export class ThemeSwitcherComponent {
  themeService = inject(ThemeService);
  private assetsService = inject(ThemeAssetsService);

  logoUrl = computed(
    () => this.assetsService.getAssetUrl(this.themeService.selectedTheme(), 'logo', this.themeService.resolvedScheme()) || '/default-logo.svg',
  );
}

4. Add Theme Names to Translations

Store theme and scheme display names in translation files. Create /assets/themes/i18n/en.json:

English (en.json):

{
  "sdk": {
    "theme": {
      "catalog": {
        "sixbell_telco": "Sixbell telco",
        "wom": "Wom"
      },
      "schemes": {
        "system": "System",
        "light": "Light",
        "dark": "Dark"
      }
    }
  }
}

Spanish (es.json):

{
  "sdk": {
    "theme": {
      "catalog": {
        "sixbell_telco": "Sixbell telco",
        "wom": "Wom"
      },
      "schemes": {
        "system": "Sistema",
        "light": "Claro",
        "dark": "Oscuro"
      }
    }
  }
}

Portuguese (pt.json):

{
  "sdk": {
    "theme": {
      "catalog": {
        "sixbell_telco": "Sixbell telco",
        "wom": "Wom"
      },
      "schemes": {
        "system": "Sistema",
        "light": "Claro",
        "dark": "Escuro"
      }
    }
  }
}

Your themes are now fully configured. The ThemeService automatically:

  • Loads theme definitions from themes.json
  • Injects CSS variables via signals
  • Resolves dynamic assets based on current theme and scheme
  • Persists user preferences to localStorage
  • Detects system dark mode preference when scheme is set to 'system'

Adding Custom Languages

Languages are now managed through a runtime configuration file (translation.json), eliminating the need to reconfigure and recompile. This approach allows for:

  • Zero Recompilation: Add or modify languages without rebuilding
  • Multi-Tenant Languages: Different language sets per deployment environment
  • Hot Updates: Changes to translation configuration take effect immediately
  • Flexible Structure: Organize translation files any way you need

1. Create or Update translation.json

Create /assets/translation/translation.json in your application:

{
  "meta": {
    "updatedAt": "2026-02-02T00:00:00Z",
    "hash": "showcase-translations-v2",
    "schemaVersion": "3"
  },
  "additionalLanguages": [],
  "defaultLang": "es",
  "excludeLanguages": [],
  "translationPaths": ["/assets/i18n/", "/assets/i18n/sdk/", "/assets/themes/i18n"],
  "locale": {
    "defaultLocale": "en-US",
    "languageToLocale": {
      "en": "en-US",
      "es": "es-CL",
      "pt": "pt-BR"
    }
  }
}

Configuration Structure:

  • defaultLang: language code used on app initialization
  • translationPaths: list of directories that contain {lang}.json
  • additionalLanguages: extra languages beyond the built-in SDK defaults
  • excludeLanguages: built-in languages to remove from the available list
  • locale.languageToLocale: mapping used by FormattingService and the formatting pipes

2. Create Translation Files

Create language translation files at the paths defined in translation.json. The minimal translation structure includes SDK translations:

English (/assets/i18n/sdk/en.json):

{
  "sdk": {
    "fileUpload": {
      "dropzone": {
        "selectPrompt": "Click to select files or drag and drop here",
        "maxSizeLabel": "Max size:",
        "allowedTypesLabel": "Allowed types:",
        "invalidFileType": "Invalid file type",
        "fileTooLarge": "File too large",
        "fileCounter": "{{current}} of {{max}} files",
        "maxFilesExceeded": "Maximum {{max}} file(s) allowed",
        "noValidFiles": "No valid files selected",
        "fileActions": {
          "play": "Play audio",
          "pause": "Pause audio",
          "download": "Download file",
          "remove": "Remove file"
        }
      },
      "fileUploader": {
        "allowedTypesLabel": "Allowed types:"
      }
    },
    "audioPlayer": {
      "trackListTitle": "Tracks",
      "trackInfo": {
        "title": "No title",
        "description": "No description"
      }
    },
    "countdown": {
      "days": "Days",
      "hours": "Hours",
      "minutes": "Minutes",
      "seconds": "Seconds"
    },
    "dualList": {
      "searchPlaceHolder": "Search"
    },
    "combobox": {
      "searchPlaceholder": "Search",
      "placeholder": "Select",
      "noResultsFound": "No results found",
      "clearAll": "Clear All",
      "clearSelection": "Clear selection",
      "searchOptions": "Search options",
      "loading": "Loading..."
    },
    "select": {
      "placeholder": "Select"
    },
    "formErrors": {
      "validation": {
        "required": "*This field is required",
        "email": "*Please enter a valid email address",
        "minLength": "*Must be at least {{min}} characters",
        "maxLength": "*Must be no more than {{max}} characters",
        "min": "*Value must be at least {{min}}",
        "max": "*Value must be no more than {{max}}",
        "pattern": "*Invalid format",
        "unhandledError": "*Unhandled error"
      }
    },
    "formFields": {
      "optional": "Optional"
    },
    "textarea": {
      "maxCharacters": "{{current}} of {{max}} characters"
    },
    "table": {
      "entriesByPage": "Entries per page",
      "perPage": "{{count}} / page",
      "totalEntries": "{{total}} entries",
      "entriesRange": "{{range}} of {{total}} entries",
      "emptyListMessage": "No items to show",
      "noData": {
        "title": "No data available",
        "message": "There are no records to display at this time."
      },
      "noResults": {
        "title": "No results found",
        "message": "Try adjusting your search criteria"
      }
    },
    "dataTable": {
      "entriesByPage": "Entries per page",
      "perPage": "{{count}} / page",
      "totalEntries": "{{total}} entries",
      "entriesRange": "{{range}} of {{total}} entries",
      "emptyListMessage": "No items to show",
      "noData": {
        "title": "No data available",
        "message": "There are no records to display at this time."
      },
      "noResults": {
        "title": "No results found",
        "message": "Try adjusting your search criteria"
      }
    },
    "wizard": {
      "wizardMarker": {
        "completed": "Completed",
        "inProgress": "In progress",
        "pending": "Pending"
      },
      "wizardWrapper": {
        "back": "Back",
        "previous": "Previous",
        "end": "End",
        "next": "Next",
        "step": "Step {{index}}"
      }
    },
    "datePicker": {
      "quickActions": {
        "title": "Quick Select",
        "lastHour": "Last hour",
        "last3Hours": "Last 3 hours",
        "last6Hours": "Last 6 hours",
        "last12Hours": "Last 12 hours",
        "last24Hours": "Last 24 hours",
        "last7Days": "Last 7 days",
        "last30Days": "Last 30 days",
        "last6Months": "Last 6 months",
        "lastYear": "Last year",
        "clear": "Clear"
      },
      "labels": {
        "from": "From",
        "to": "To"
      }
    }
  }
}

Spanish (/assets/i18n/es.json):

{
  "sdk": {
    "fileUpload": {
      "dropzone": {
        "selectPrompt": "Haz clic para seleccionar archivos o arrastra y suelta aquí",
        "maxSizeLabel": "Tamaño máximo:",
        "allowedTypesLabel": "Tipos permitidos:",
        "invalidFileType": "Tipo de archivo no válido",
        "fileTooLarge": "Archivo demasiado grande",
        "fileCounter": "{{current}} de {{max}} archivos",
        "maxFilesExceeded": "Máximo de {{max}} archivo(s) permitido",
        "noValidFiles": "No se han seleccionado archivos válidos",
        "fileActions": {
          "play": "Reproducir audio",
          "pause": "Pausar audio",
          "download": "Descargar archivo",
          "remove": "Quitar archivo"
        }
      },
      "fileUploader": {
        "allowedTypesLabel": "Tipos permitidos:"
      }
    },
    "audioPlayer": {
      "trackListTitle": "Pistas",
      "trackInfo": {
        "title": "Sin título",
        "description": "Sin descripción"
      }
    },
    "countdown": {
      "days": "Días",
      "hours": "Horas",
      "minutes": "Minutos",
      "seconds": "Segundos"
    },
    "dualList": {
      "searchPlaceHolder": "Buscar"
    },
    "combobox": {
      "searchPlaceholder": "Buscar",
      "placeholder": "Seleccione",
      "noResultsFound": "No se encontraron resultados",
      "clearAll": "Limpiar Todo",
      "clearSelection": "Limpiar selección",
      "searchOptions": "Buscar opciones",
      "loading": "Cargando..."
    },
    "select": {
      "placeholder": "Seleccione"
    },
    "formErrors": {
      "validation": {
        "required": "*Este campo es obligatorio",
        "email": "*Por favor ingresa una dirección de correo válida",
        "minLength": "*Debe tener al menos {{min}} caracteres",
        "maxLength": "*No debe tener más de {{max}} caracteres",
        "min": "*El valor debe ser al menos {{min}}",
        "max": "*El valor no debe superar {{max}}",
        "pattern": "*Formato inválido",
        "unhandledError": "*Error inesperado"
      }
    },
    "textarea": {
      "maxCharacters": "{{current}} de {{max}} caracteres"
    },
    "table": {
      "entriesByPage": "Entradas por página",
      "perPage": "{{count}} / página",
      "totalEntries": "{{total}} entradas",
      "entriesRange": "{{range}} de {{total}} entradas",
      "emptyListMessage": "No hay elementos para mostrar"
    },
    "dataTable": {
      "entriesByPage": "Entradas por página",
      "perPage": "{{count}} / página",
      "totalEntries": "{{total}} entradas",
      "entriesRange": "{{range}} de {{total}} entradas",
      "emptyListMessage": "No hay elementos para mostrar",
      "noData": {
        "title": "No hay datos disponibles",
        "message": "No hay registros para mostrar en este momento."
      },
      "noResults": {
        "title": "No se encontraron resultados",
        "message": "Intenta ajustar tus criterios de búsqueda"
      }
    },
    "wizard": {
      "wizardMarker": {
        "completed": "Completado",
        "inProgress": "En progreso",
        "pending": "Pendiente"
      },
      "wizardWrapper": {
        "back": "Atrás",
        "previous": "Anterior",
        "end": "Finalizar",
        "next": "Siguiente",
        "step": "Paso {{index}}"
      }
    },
    "datePicker": {
      "quickActions": {
        "title": "Selección Rápida",
        "lastHour": "Última hora",
        "last3Hours": "Últimas 3 horas",
        "last6Hours": "Últimas 6 horas",
        "last12Hours": "Últimas 12 horas",
        "last24Hours": "Últimas 24 horas",
        "last7Days": "Últimos 7 días",
        "last30Days": "Últimos 30 días",
        "last6Months": "Últimos 6 meses",
        "lastYear": "Último año",
        "clear": "Limpiar"
      },
      "labels": {
        "from": "Desde",
        "to": "Hasta"
      }
    }
  }
}

Portuguese (/assets/i18n/pt.json):

{
  "sdk": {
    "fileUpload": {
      "dropzone": {
        "selectPrompt": "Haz clic para seleccionar archivos o arrastra y suelta aquí",
        "maxSizeLabel": "Tamaño máximo:",
        "allowedTypesLabel": "Tipos permitidos:",
        "invalidFileType": "Tipo de archivo no válido",
        "fileTooLarge": "Archivo demasiado grande",
        "fileCounter": "{{current}} de {{max}} archivos",
        "maxFilesExceeded": "Máximo de {{max}} archivo(s) permitido",
        "noValidFiles": "No se han seleccionado archivos válidos",
        "fileActions": {
          "play": "Reproducir audio",
          "pause": "Pausar audio",
          "download": "Descargar archivo",
          "remove": "Quitar archivo"
        }
      },
      "fileUploader": {
        "allowedTypesLabel": "Tipos permitidos:"
      }
    },
    "audioPlayer": {
      "trackListTitle": "Trilhas",
      "trackInfo": {
        "title": "Sem título",
        "description": "Sem descrição"
      }
    },
    "countdown": {
      "days": "Dias",
      "hours": "Horas",
      "minutes": "Minutos",
      "seconds": "Segundos"
    },
    "dualList": {
      "searchPlaceHolder": "Pesquisar"
    },
    "combobox": {
      "searchPlaceholder": "Pesquisar",
      "placeholder": "Selecione",
      "noResultsFound": "Nenhum resultado encontrado",
      "clearAll": "Limpar Tudo",
      "clearSelection": "Limpar seleção",
      "searchOptions": "Pesquisar opções",
      "loading": "Carregando..."
    },
    "select": {
      "placeholder": "Selecione"
    },
    "formErrors": {
      "validation": {
        "required": "*Este campo é obrigatório",
        "email": "*Por favor, insira um endereço de e-mail válido",
        "minLength": "*Deve ter pelo menos {{min}} caracteres",
        "maxLength": "*Deve ter no máximo {{max}} caracteres",
        "min": "*O valor deve ser de pelo menos {{min}}",
        "max": "*O valor deve ser de no máximo {{max}}",
        "pattern": "*Formato inválido",
        "unhandledError": "*Erro não tratado"
      }
    },
    "textarea": {
      "maxCharacters": "{{current}} de {{max}} caracteres"
    },
    "table": {
      "entriesByPage": "Entradas por página",
      "perPage": "{{count}} / página",
      "totalEntries": "{{total}} entradas",
      "entriesRange": "{{range}} de {{total}} entradas",
      "emptyListMessage": "Nenhum item para mostrar"
    },
    "dataTable": {
      "entriesByPage": "Entradas por página",
      "perPage": "{{count}} / página",
      "totalEntries": "{{total}} entradas",
      "entriesRange": "{{range}} de {{total}} entradas",
      "emptyListMessage": "Nenhum item para mostrar",
      "noData": {
        "title": "Nenhum dado disponível",
        "message": "Não há registros para exibir neste momento."
      },
      "noResults": {
        "title": "Nenhum resultado encontrado",
        "message": "Tente ajustar seus critérios de pesquisa"
      }
    },
    "wizard": {
      "wizardMarker": {
        "completed": "Concluído",
        "inProgress": "Em andamento",
        "pending": "Pendente"
      },
      "wizardWrapper": {
        "back": "Voltar",
        "previous": "Anterior",
        "end": "Fim",
        "next": "Próximo",
        "step": "Etapa {{index}}"
      }
    },
    "datePicker": {
      "quickActions": {
        "title": "Seleção Rápida",
        "lastHour": "Última hora",
        "last3Hours": "Últimas 3 horas",
        "last6Hours": "Últimas 6 horas",
        "last12Hours": "Últimas 12 horas",
        "last24Hours": "Últimas 24 horas",
        "last7Days": "Últimos 7 dias",
        "last30Days": "Últimos 30 dias",
        "last6Months": "Últimos 6 meses",
        "lastYear": "Último ano",
        "clear": "Limpar"
      },
      "labels": {
        "from": "De",
        "to": "Até"
      }
    }
  }
}

3. Access Language Functionality in Components

The TranslationService automatically loads available languages from translation.json:

// In any component
import { TranslationService } from '@sixbell-telco/sdk/utils/translation';
import { inject } from '@angular/core';

export class MyComponent {
  private translationService = inject(TranslationService);

  availableLanguages = this.translationService.getAvailableLanguagesConfig();
  currentLanguage = this.translationService.currentLanguage;

  setLanguage(languageCode: string) {
    this.translationService.setLanguage(languageCode);
  }

  getLanguageName(languageCode: string): string {
    return this.translationService.getLocalizedLanguageName(languageCode);
  }
}

4. Use Translations in Templates

Translations are automatically loaded and available through the translate pipe:

<!-- Access any translation key -->
<h1>{{ 'ui.showcase' | translate }}</h1>

<!-- With parameters -->
<p>{{ 'welcome.message' | translate: { name: userName } }}</p>

<!-- In component -->
<select (change)="setLanguage($event.target.value)">
  @for (lang of availableLanguages; track lang.code) {
  <option [value]="lang.code">{{ getLanguageName(lang.code) }}</option>
  }
</select>

5. Add Custom Application Translations

Extend the translation files with your application-specific keys:

English (/assets/i18n/en.json):

{
  "app": {
    "title": "My Application",
    "welcome": "Welcome to our app",
    "features": {
      "description": "Powerful features for your business"
    }
  },
  "sdk": {
    "formFields": {
      "optional": "Optional"
    }
  }
}

Your application is now configured with full multi-language support. Language preferences are automatically persisted and restored on return visits.

Usage Examples

Using Components

Here's a basic example of using a button component:

// app.component.ts
import { ButtonComponent } from '@sixbell-telco/sdk/components/button';

@Component({
  imports: [ButtonComponent],
  template: `
    <div>
      <h1>Awesome button</h1>
      <st-button variant="primary">Click me!</st-button>
    </div>
  `
})

Using Text Directive & Component

The Text Directive (stText) applies preset-based typography styles directly on any HTML element. The Text Component (st-text) is a polymorphic wrapper that renders the correct semantic HTML tag.

Both support class overrides — any Tailwind class you put in class="" will override the corresponding directive/component default via tailwind-merge.

Text Directive

import { TextDirective } from '@sixbell-telco/sdk/directives/text';

@Component({
  imports: [TextDirective],
  template: `
    <!-- Basic presets (1 = largest heading, 10 = smallest body) -->
    <h1 stText="1">Hero Title</h1>
    <p stText="7">Body text (default preset)</p>
    <span stText="9">Small label</span>

    <!-- Override weight and color via inputs -->
    <p stText="7" stTextWeight="bold" stTextColor="primary">Bold primary text</p>

    <!-- Override any prop via class (wins over defaults) -->
    <p stText="7" class="text-error font-semibold">Error text with semibold weight</p>
    <p stText="7" class="text-3xl leading-loose">Larger size and loose line-height</p>

    <!-- Line clamp -->
    <p stText="7" stTextLineClamp="2">Long text clamped to 2 lines...</p>
  `
})

Available inputs:

| Input | Default | Options | | ----------------- | ----------- | ----------------------------------------------------------------------------------- | | stText | '7' | '1''10' (preset scale) | | stTextWeight | 'normal' | 'bold', 'semibold', 'medium', 'normal', 'light', 'inherit' | | stTextColor | 'inherit' | 'base', 'base-placeholder', 'primary', 'secondary', 'accent', 'inherit' | | stTextOverflow | 'clip' | 'clip', 'ellipsis', 'truncate' | | stTextLineClamp | undefined | '1''6', 'none' |

Class overrides: Any Tailwind utility in class="" overrides the corresponding directive default (color, weight, font-size, line-height, tracking, etc.) via tailwind-merge.

Text Component

import { TextComponent } from '@sixbell-telco/sdk/components/text';

@Component({
  imports: [TextComponent],
  template: `
    <!-- Semantic tags with presets -->
    <st-text as="h1" preset="1">Hero Title</st-text>
    <st-text as="h2" preset="2">Page Title</st-text>
    <st-text as="p" preset="7">Body paragraph</st-text>
    <st-text as="span" preset="9" color="primary">Inline label</st-text>

    <!-- Override via inputs -->
    <st-text as="p" preset="7" weight="bold" color="accent">Bold accent text</st-text>

    <!-- Override via class (wins over defaults) -->
    <st-text as="p" preset="7" class="text-error font-medium">Custom error text</st-text>
    <st-text as="h1" preset="1" class="mb-4 border-b border-accent pb-2">Heading with border</st-text>
  `
})

Available inputs:

| Input | Default | Options | | ----------- | ----------- | ----------------------------------------------------------------------------------- | | as | 'p' | 'h1''h6', 'p', 'span', 'div', 'label' | | preset | '7' | '1''10' | | weight | undefined | 'bold', 'semibold', 'medium', 'normal', 'light', 'inherit' | | color | 'base' | 'base', 'base-placeholder', 'primary', 'secondary', 'accent', 'inherit' | | overflow | 'clip' | 'clip', 'ellipsis', 'truncate' | | lineClamp | undefined | '1''6', 'none' |

Using Services in Components

// component.ts
import { ThemeService } from '@sixbell-telco/sdk/utils/theme';
import { TranslationService } from '@sixbell-telco/sdk/utils/translation';

@Component({
  template: `
    <div [class]="themeService.finalTheme()">
      <h1>{{ 'welcome.title' | translate }}</h1>
      <p>Current theme: {{ themeService.selectedTheme() }}</p>
      <p>Dark mode: {{ themeService.isDarkTheme() ? 'Yes' : 'No' }}</p>

      <!-- Theme selector -->
      <select (change)="changeTheme($event.target.value)">
        @for (theme of themeService.getAvailableThemes(); track theme) {
          <option [value]="theme">{{ theme }}</option>
        }
      </select>

      <!-- Language selector -->
      <select (change)="changeLanguage($event.target.value)">
        @for (lang of translationService.getAvailableLanguagesConfig(); track lang.code) {
          <option [value]="lang.code">{{ translationService.getLocalizedLanguageName(lang.code) }}</option>
        }
      </select>
    </div>
  `,
})
export class MyComponent {
  themeService = inject(ThemeService);
  translationService = inject(TranslationService);

  changeTheme(theme: string) {
    this.themeService.setTheme(theme);
  }

  changeLanguage(language: string) {
    this.translationService.setLanguage(language);
  }
}

Quick Start

Using Components

import { Component } from '@angular/core';
import { ButtonComponent } from '@sixbell-telco/sdk/components/button';
import {
  CardActionsComponent,
  CardBodyComponent,
  CardComponent,
  CardFooterComponent,
  CardHeaderComponent,
  CardSectionBodyComponent,
  CardSectionComponent,
  CardSectionGroupComponent,
  CardSubtitleComponent,
  CardTitleComponent,
} from '@sixbell-telco/sdk/components/card';

@Component({
  selector: 'app-example',
  standalone: true,
  imports: [
    ButtonComponent,
    CardActionsComponent,
    CardBodyComponent,
    CardComponent,
    CardFooterComponent,
    CardHeaderComponent,
    CardSectionBodyComponent,
    CardSectionComponent,
    CardSectionGroupComponent,
    CardSubtitleComponent,
    CardTitleComponent,
  ],
  template: `
    <st-card>
      <st-card-header>
        <st-card-title>Hello World</st-card-title>
        <st-card-subtitle>Small supporting text</st-card-subtitle>
      </st-card-header>
      <st-card-body>
        <st-card-section-group [divided]="true">
          <st-card-section>
            <st-card-section-body>Welcome to the new compound card API.</st-card-section-body>
          </st-card-section>
        </st-card-section-group>
      </st-card-body>
      <st-card-footer [divided]="true">
        <st-card-actions>
          <st-button variant="primary">Click me!</st-button>
        </st-card-actions>
      </st-card-footer>
    </st-card>
  `,
})
export class ExampleComponent {}

Using Form Components

import { Component, signal } from '@angular/core';
import { ReactiveFormsModule, FormControl, FormGroup } from '@angular/forms';
import { InputComponent } from '@sixbell-telco/sdk/components/forms/input';
import { ButtonComponent } from '@sixbell-telco/sdk/components/button';

@Component({
  selector: 'app-form-example',
  standalone: true,
  imports: [InputComponent, ButtonComponent, ReactiveFormsModule],
  template: `
    <form [formGroup]="form">
      <st-input formControlName="email" placeholder="Enter email"></st-input>
      <st-button (click)="submit()">Submit</st-button>
    </form>
  `,
})
export class FormExampleComponent {
  form = new FormGroup({
    email: new FormControl(''),
  });

  submit() {
    console.log(this.form.value);
  }
}

Using Services

import { Component, inject, effect } from '@angular/core';
import { ThemeService } from '@sixbell-telco/sdk/utils/theme';
import { TranslationService } from '@sixbell-telco/sdk/utils/translation';
import { LoggerService } from '@sixbell-telco/sdk/utils/logger';

@Component({
  selector: 'app-services-example',
  template: `
    <div>
      <p>Current Theme: {{ themeService.selectedTheme() }}</p>
      <p>Dark Mode: {{ themeService.isDarkTheme() ? 'Yes' : 'No' }}</p>
      <button (click)="toggleTheme()">Toggle Theme</button>
    </div>
  `,
})
export class ServicesExampleComponent {
  themeService = inject(ThemeService);
  translationService = inject(TranslationService);
  logger = inject(LoggerService);

  constructor() {
    effect(() => {
      this.logger.info(`Theme changed to: ${this.themeService.selectedTheme()}`);
    });
  }

  toggleTheme() {
    const newScheme = this.themeService.isDarkTheme() ? 'light' : 'dark';
    this.themeService.setScheme(newScheme);
  }
}

Configuration

Theme Configuration

Runtime themes are loaded from themes.json. See Theme Service README for detailed configuration.

Translation Configuration

Runtime translations are loaded from translation.json. See Translation Service README for detailed configuration.

Logger Configuration

Configure logging levels and output. See Logger Service README for detailed options.

Architecture

Multi-Entry Point Imports

Always use specific imports for optimal tree-shaking:

// ✅ Correct - specific imports
import { ButtonComponent } from '@sixbell-telco/sdk/components/button';
import { ThemeService } from '@sixbell-telco/sdk/utils/theme';

// ❌ Avoid - barrel exports
import { ButtonComponent } from '@sixbell-telco/sdk';

Component Prefix

All SDK components use the st- prefix:

<st-button>Button</st-button>
<st-input type="text" />
<st-card>Card Content</st-card>
<st-divider>OR</st-divider>

Signal-Based API

Components use Angular 19 signals for reactive inputs:

variant = input<'primary' | 'secondary'>();
isDisabled = input(false);

@Component({
  template: `<button [disabled]="isDisabled()">{{ variant() }}</button>`,
})

Testing

The SDK includes Jest configuration. Run tests with:

npm test                # Run all tests
npm run test:watch    # Watch mode
npm run test:coverage # Coverage report

See .agents/skills/angular-testing-jest-ngmocks/SKILL.md for Angular 19 testing patterns.

Documentation

  • Component Stories: View component documentation in Storybook
  • Live Examples: Showcase app at projects/showcase/
  • API Documentation: JSDoc comments in component files
  • Changelog: See CHANGELOG.md

Agent skills release scope

The SDK skill at .agents/skills/sixbell-sdk/ follows the same lifecycle as projects/sdk/CHANGELOG.md:

  1. During active development, the skill metadata must point to Unreleased.
  2. When a release is created, update the skill metadata to the released version or tag.
  3. When development starts again, switch the skill metadata back to Unreleased.

This keeps the skill aligned with the current codebase instead of the last published package.

Contributing

When creating new components:

  1. Use st- prefix for selectors
  2. Follow CVA pattern for styling variants
  3. Use signal-based inputs
  4. Add comprehensive JSDoc comments
  5. Create Storybook stories
  6. Add unit tests with Jest
  7. Update CHANGELOG.md

Resources

  • Angular Documentation
  • Tailwind CSS
  • DaisyUI
  • class-variance-authority
  • ng-icons
  • Angular CDK

License

MIT - See LICENSE file for details

Support

For issues, questions, or feature requests, please visit the project repository or contact the Sixbell Telco team.


Last updated: March 19, 2026