nvx-ui
v0.7.1
Published
Angular component library — Arabic/English, Tailwind v4, i18n, dark mode, RTL, reactive forms, CDK Overlay.
Downloads
3,449
Readme
nvx-ui
Angular component library — Arabic/English, Tailwind v4, i18n, dark mode, RTL, reactive forms, CDK Overlay.
Install
npm install nvx-uiAuto-installs @angular/cdk and @lucide/angular (icons).
You also need Tailwind v4:
npm install tailwindcss @tailwindcss/postcss postcssQuick Setup
angular.json
"builder": "@angular/build:application".postcssrc.json
{
"plugins": { "@tailwindcss/postcss": {} }
}src/styles.css
@import "nvx-ui/index.css";
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--color-surface: oklch(100% 0 0);
--color-text: oklch(14.48% 0 0);
--color-border: oklch(87% 0 0);
--color-primary: oklch(0.62 0.24 30);
--color-danger: oklch(0.64 0.28 210);
--color-gray-100: oklch(96% 0 0);
--color-gray-800: oklch(27% 0 0);
--color-gray-500: oklch(55% 0 0);
}
.dark {
--color-surface: oklch(14.48% 0 0);
--color-text: oklch(98.50% 0 0);
--color-border: oklch(30% 0 0);
}app.config.ts
import { ApplicationConfig, provideZonelessChangeDetection } from '@angular/core';
import { I18N_CONFIG, VIEW_TRANSITION_CONFIG } from 'nvx-ui/core';
export const appConfig: ApplicationConfig = {
providers: [
provideZonelessChangeDetection(),
{ provide: I18N_CONFIG, useValue: { defaultLang: 'ar', storageKey: 'app_lang' } },
{ provide: VIEW_TRANSITION_CONFIG, useValue: { disabled: true } },
],
};Entry Points
Do NOT import from the main barrel (
nvx-ui) AND individual entries in the same app — component ID collision (NG0912).
| Import | CDK | Size |
|--------|-----|------|
| nvx-ui (barrel) | ✅ | 157 KB |
| nvx-ui/core | ❌ | 15 KB |
| nvx-ui/forms | ❌ | 10 KB |
| nvx-ui/input | ❌ | 15 KB |
| nvx-ui/toggle | ❌ | 9 KB |
| nvx-ui/checkbox | ❌ | 9 KB |
| nvx-ui/textarea | ❌ | 9 KB |
| nvx-ui/phone-input | ❌ | 10 KB |
| nvx-ui/national-id-input | ❌ | 9 KB |
| nvx-ui/radio-group | ❌ | 11 KB |
| nvx-ui/file-input | ❌ | 15 KB |
| nvx-ui/error-message | ❌ | 6 KB |
| nvx-ui/lang-toggle | ❌ | 9 KB |
| nvx-ui/theme-toggle | ❌ | 8 KB |
| nvx-ui/dropdown | ✅ | 25 KB |
| nvx-ui/autocomplete | ✅ | 23 KB |
| nvx-ui/date-input | ✅ | 41 KB |
| nvx-ui/multiselect | ✅ | 26 KB |
Config Tokens
I18N_CONFIG (from nvx-ui/core)
interface I18nConfig {
defaultLang: 'ar' | 'en'; // default: 'ar'
storageKey: string; // default: 'app_lang'
}VIEW_TRANSITION_CONFIG (from nvx-ui/core)
interface ViewTransitionConfig {
disabled?: boolean; // default: false
}DROPDOWN_CONFIG (from nvx-ui/dropdown)
interface DropdownConfig {
searchPlaceholder?: string;
noResultsText?: string;
debounceTime: number; // default: 150
}MULTISELECT_CONFIG (from nvx-ui/multiselect)
interface MultiselectConfig {
searchPlaceholder?: string;
noResultsText?: string;
maxDisplayItems?: number; // default: 2
debounceTime?: number; // default: 150
}AUTOCOMPLETE_CONFIG (from nvx-ui/autocomplete)
interface AutocompleteConfig {
noResultsText?: string;
debounceTime?: number; // default: 150
}Services
TranslationService
import { TranslationService } from 'nvx-ui/core';
t = inject(TranslationService);
t.translate('مرحباً', 'Hello'); // returns ar/en based on current lang
t.toggle(); // ar ↔ en
t.setLang('en'); // force language
t.currentLang(); // Signal<'ar' | 'en'>
t.dir(); // Signal<'rtl' | 'ltr'>
t.isRtl(); // Signal<boolean>ThemeService
import { ThemeService } from 'nvx-ui/core';
theme = inject(ThemeService);
theme.toggle(); // light ↔ dark
theme.setTheme('dark');
theme.isDark(); // Signal<boolean>
theme.currentTheme(); // Signal<'light' | 'dark'>Persists to localStorage, applies .dark class on <html>.
ViewTransitionService
import { ViewTransitionService } from 'nvx-ui/core';
vt = inject(ViewTransitionService);
vt.start(() => { /* DOM changes */ }, x?, y?); // circle-reveal animation
vt.isTransitioning(); // Signal<boolean>Form System
createForm + FormField
import { createForm } from 'nvx-ui/forms';
fields = createForm({
name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
age: [0, [Validators.min(18), Validators.max(99)]],
});
fields.group // FormGroup
fields.name // FormField<string>
fields.name.value // Signal<string>
fields.name.showError() // Signal<boolean> — invalid && touched
fields.name.activeErrors() // Signal<{ar: string, en: string}[]>
fields.name.hint() // Signal<{ar, en} | null>
fields.submit() // boolean — marks all touched, returns validityCustom error messages
fields = createForm({
name: ['', Validators.required, {
required: { ar: 'الاسم مطلوب', en: 'Name is required' }
}],
age: [0, [Validators.min(18), Validators.max(99)], {
min: { ar: 'يجب أن يكون 18 على الأقل', en: 'Must be at least 18' },
max: { ar: 'يجب أن يكون 99 على الأكثر', en: 'Must be at most 99' },
}],
});appField Directive
<app-input formControlName="name" [appField]="fields.name" />
<app-phone-input formControlName="phone" [appField]="fields.phone" />
<app-textarea formControlName="bio" [appField]="fields.bio" />
<app-toggle formControlName="agree" [appField]="fields.agree" />
<app-date-input formControlName="dob" [appField]="fields.dob" />
<app-file-input formControlName="avatar" [appField]="fields.avatar" />
<app-radio-group formControlName="gender" [appField]="fields.gender" />Import: FormFieldDirective from nvx-ui/forms.
Components
<app-input>
<app-input formControlName="name" label="Name" placeholder="Enter name" />
<app-input type="password" label="Password" showPasswordToggle />
<app-input label="Email" clearable placeholder="[email protected]" />| Input | Type | Default |
|-------|------|---------|
| type | 'text' \| 'password' | 'text' |
| label | string | — |
| placeholder | string | — |
| hint | string | — |
| disabled | boolean | false |
| withError | boolean | false |
| required | boolean | false |
| showPasswordToggle | boolean | false |
| clearable | boolean | false |
| autocomplete | string | 'on' |
Slots: prefix, suffix
<app-phone-input>
Egyptian phone — numeric only, max 11 digits. Pattern: /^01[0125]\d{8}$/
<app-phone-input formControlName="phone" label="Phone" placeholder="01xxxxxxxxx" />| Input | Type | Default |
|-------|------|---------|
| label | string | — |
| placeholder | string | '01xxxxxxxxx' |
| disabled | boolean | false |
| withError | boolean | false |
<app-national-id-input>
Egyptian national ID — numeric only, max 14 digits. Pattern: /^[23]\d{13}$/ + birth date validation.
<app-national-id-input formControlName="nationalId" label="National ID" />| Input | Type | Default |
|-------|------|---------|
| label | string | — |
| placeholder | string | 'xxxxxxxxxxxxxxxx' |
| disabled | boolean | false |
| withError | boolean | false |
<app-textarea>
<app-textarea formControlName="bio" label="Bio" rows="4" resize="vertical" />| Input | Type | Default |
|-------|------|---------|
| label | string | — |
| placeholder | string | — |
| hint | string | — |
| disabled | boolean | false |
| withError | boolean | false |
| rows | number | 4 |
| resize | 'none' \| 'vertical' \| 'horizontal' \| 'both' | 'vertical' |
<app-toggle>
<app-toggle formControlName="agree" label="Accept terms" />| Input | Type | Default |
|-------|------|---------|
| label | string | — |
| disabled | boolean | false |
<app-checkbox>
<app-checkbox formControlName="agree" label="I agree" />
<app-checkbox label="Select all" [indeterminate]="true" />| Input | Type | Default |
|-------|------|---------|
| label | string | — |
| hint | string | — |
| disabled | boolean | false |
| indeterminate | boolean | false |
<app-radio-group> + <app-radio-option>
<app-radio-group formControlName="gender" label="Gender">
<app-radio-option value="male" label="Male" />
<app-radio-option value="female" label="Female" />
</app-radio-group>RadioGroup: label, disabled
RadioOption:
| Input | Type | Default |
|-------|------|---------|
| value | any | — |
| label | string | — |
| disabled | boolean | false |
<app-date-input>
CDK Overlay calendar — keyboard nav, month/year panels, disabled dates.
<app-date-input formControlName="dob" label="Date of Birth" [disabledDate]="disableWeekends" />disableWeekends = (date: Date) => date.getDay() === 0 || date.getDay() === 6;| Input | Type | Default |
|-------|------|---------|
| label | string | — |
| placeholder | string | — |
| disabled | boolean | false |
| disabledDate | (date: Date) => boolean | — |
| clearable | boolean | false |
Value: string (ISO YYYY-MM-DD).
<app-file-input>
Drag-drop + click, image preview, file validation.
<app-file-input formControlName="avatar" label="Upload Avatar" accept="image/*" />| Input | Type | Default |
|-------|------|---------|
| label | string | — |
| hint | string | — |
| disabled | boolean | false |
| withError | boolean | false |
| accept | string | — |
Value: File | null.
<app-dropdown>
Searchable select with CDK Overlay, keyboard nav, i18n labels.
import { type DropdownOption } from 'nvx-ui/dropdown';
options: DropdownOption[] = [
{ value: 1, labelAr: 'خيار 1', labelEn: 'Option 1' },
];<app-dropdown [options]="options" placeholder="Select..." [(value)]="selected" />
<!-- With raw data + transform -->
<app-dropdown [data]="users" [transform]="userToOption" />| Input | Type | Default |
|-------|------|---------|
| options | DropdownOption[] | [] |
| data | any[] | — |
| transform | (item) => DropdownOption | — |
| placeholder | string | — |
| value | any | — (two-way: [(value)]) |
| disabled | boolean | false |
| required | boolean | false |
| clearable | boolean | false |
| errorId | string | — |
| loading | boolean | false |
Output: valueChange
<app-multiselect>
Checkbox options with chips, "+N more" overflow, search.
<app-multiselect [options]="options" [value]="selectedValues" (valueChange)="selectedValues = $event" [selectAll]="true" />Important: Always pass
[value]alongside(valueChange)— the internal effect resets selection if[value]is missing.
| Input | Type | Default |
|-------|------|---------|
| options | DropdownOption[] | [] |
| data | any[] | — |
| transform | (item) => DropdownOption | — |
| value | any[] | [] |
| disabled | boolean | false |
| required | boolean | false |
| errorId | string | — |
| selectAll | boolean | false |
| loading | boolean | false |
Output: valueChange — emits any[]
<app-autocomplete>
Searchable input with CDK Overlay dropdown, keyboard nav, clearable.
<app-autocomplete [options]="options" [(value)]="selected" placeholder="Search..." />| Input | Type | Default |
|-------|------|---------|
| options | DropdownOption[] | [] |
| data | any[] | — |
| transform | (item) => DropdownOption | — |
| placeholder | string | — |
| value | any | — (two-way: [(value)]) |
| disabled | boolean | false |
| required | boolean | false |
| clearable | boolean | false |
| errorId | string | — |
| loading | boolean | false |
Output: valueChange
<app-error-message>
<app-error-message [errors]="fields.name.activeErrors()" />| Input | Type | Default |
|-------|------|---------|
| errors | { ar: string; en: string }[] | [] |
| visible | boolean | false |
<app-lang-toggle>
<app-lang-toggle />No inputs/outputs — auto toggles via TranslationService. Triggers 3D page-flip view transition.
<app-theme-toggle>
<app-theme-toggle />No inputs/outputs — auto toggles via ThemeService. Triggers circle-reveal view transition.
API Reference
| Component | Selector | Form Value |
|-----------|----------|-----------|
| InputComponent | app-input | string |
| PhoneInputComponent | app-phone-input | string |
| NationalIdInputComponent | app-national-id-input | string |
| TextareaComponent | app-textarea | string |
| ToggleComponent | app-toggle | boolean |
| CheckboxComponent | app-checkbox | boolean |
| RadioGroupComponent | app-radio-group | any |
| DateInputComponent | app-date-input | string (ISO) |
| FileInputComponent | app-file-input | File \| null |
| DropdownComponent | app-dropdown | — |
| MultiselectComponent | app-multiselect | — |
| AutocompleteComponent | app-autocomplete | — |
| ErrorMessageComponent | app-error-message | — |
| LangToggleComponent | app-lang-toggle | — |
| ThemeToggleComponent | app-theme-toggle | — |
| Directive | Selector |
|-----------|----------|
| FormFieldDirective | [appField] |
| Service | Entry Point |
|---------|-------------|
| TranslationService | nvx-ui/core |
| ThemeService | nvx-ui/core |
| ViewTransitionService | nvx-ui/core |
| Injection Token | Entry Point |
|-----------------|-------------|
| I18N_CONFIG | nvx-ui/core |
| VIEW_TRANSITION_CONFIG | nvx-ui/core |
| DROPDOWN_CONFIG | nvx-ui/dropdown |
| MULTISELECT_CONFIG | nvx-ui/multiselect |
| AUTOCOMPLETE_CONFIG | nvx-ui/autocomplete |
