Componentes
Ejemplos de aplicación
Flujos reales construidos con componentes nativos públicos.
Estos ejemplos funcionan bajo NeuTheme y un proveedor de área segura. El estado permanece en memoria: conecta tu persistencia en la aplicación final. El código compila contra el paquete distribuido.
Formulario de bienvenida
Completa el perfil, elige equipo, configura preferencias y revisa la confirmación de cuenta simulada. No se crea una cuenta en un servidor.
Stepper · Card · Input · Select · Switch · Checkbox · Badge
Capturas del dispositivo en inglés.


import { Component, computed, signal } from '@angular/core';
import { Text, View } from '@ng-native/components';
import { NeuBadgeComponent, NeuCardComponent, NeuCheckboxComponent, NeuInputComponent,
NeuSelectComponent, NeuStepperComponent, NeuSwitchComponent, type NeuOption } from '@neural-ui/native';
import { validEmail, validProfile } from './logic.js';
@Component({
imports: [Text, View, NeuBadgeComponent, NeuCardComponent, NeuCheckboxComponent, NeuInputComponent,
NeuSelectComponent, NeuStepperComponent, NeuSwitchComponent],
template: `
@if (finished()) {
<neu-card title="Your account is ready" description="You can start working with your team.">
<neu-badge label="All set" />
<text class="body">{{ name() }} · {{ email() }}</text>
</neu-card>
} @else {
<neu-stepper [steps]="steps" [(activeStep)]="step" [linear]="true" [completed]="completed()"
previousLabel="Back" nextLabel="Continue" finishLabel="Create account" (finished)="finish()">
@switch (step()) {
@case (0) {
<neu-card title="Let’s get to know you" description="A profile to collaborate from day one.">
<neu-input label="Full name" [(value)]="name" [invalid]="name().trim().length < 2" error="Enter your name" />
<neu-input label="Email address" keyboardType="email-address" [(value)]="email"
[invalid]="!emailValid()" error="Enter a valid email address" />
<neu-select label="Team" [options]="teams" [(value)]="team" />
</neu-card>
}
@case (1) {
<neu-card title="Your way" description="Choose how to stay up to date.">
<neu-switch label="Team updates" [(checked)]="updates" />
<neu-checkbox label="I accept the terms" [(checked)]="accepted" />
<text class="hint">You can change notifications at any time.</text>
</neu-card>
}
@case (2) {
<neu-card title="Review your profile" description="Check your details before getting started.">
<view class="summary"><text class="body">{{ name() }}</text><text class="hint">{{ email() }}</text></view>
<neu-badge [label]="teamLabel()" />
<text class="body">{{ updates() ? 'You will receive team updates.' : 'Notifications are off.' }}</text>
</neu-card>
}
}
</neu-stepper>
}
`,
styles: `
.summary { gap: 4px; }
.body { color: var(--neu-text, #172033); font-size: 16px; }
.hint { color: var(--neu-muted, #596477); font-size: 14px; }
`,
})
export class OnboardingRecipe {
readonly steps: readonly NeuOption[] = [{ id: 'profile', label: 'Profile' }, { id: 'preferences', label: 'Preferences' }, { id: 'review', label: 'Summary' }];
readonly teams: readonly NeuOption[] = [{ id: 'design', label: 'Design' }, { id: 'product', label: 'Product' }, { id: 'engineering', label: 'Engineering' }];
readonly name = signal('Ada Lovelace');
readonly email = signal('ada@neuralui.org');
readonly team = signal('design');
readonly updates = signal(true);
readonly accepted = signal(false);
readonly step = signal(0);
readonly finished = signal(false);
readonly emailValid = computed(() => validEmail(this.email()));
readonly profileValid = computed(() => validProfile(this.name(), this.email(), this.team()));
readonly teamLabel = computed(() => this.teams.find(team => team.id === this.team())?.label ?? 'Team');
readonly completed = computed(() => [
...(this.profileValid() ? ['profile'] : []),
...(this.accepted() ? ['preferences'] : []),
...(this.profileValid() && this.accepted() ? ['review'] : []),
]);
finish(): void { if (this.profileValid() && this.accepted()) this.finished.set(true); }
}
export interface Project { readonly id: string; readonly label: string; readonly description: string; readonly status: 'active' | 'review' | 'done'; }
export const PROJECTS: readonly Project[] = [
{ id: 'native', label: 'Neural Launch', description: 'A mobile experience for your team', status: 'active' },
{ id: 'studio', label: 'Design studio', description: 'Ideas ready for the next review', status: 'review' },
{ id: 'guide', label: 'Welcome guide', description: 'The first step for every new member', status: 'done' },
];
const normalize = (value: string) => value.trim().normalize('NFD').replace(/\p{Diacritic}/gu, '').toLocaleLowerCase('en');
export function validEmail(email: string): boolean { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim()); }
export function validProfile(name: string, email: string, team: string): boolean {
return name.trim().length >= 2 && validEmail(email) && ['design', 'product', 'engineering'].includes(team);
}
export function filterProjects(query: string, statuses: readonly string[]): readonly Project[] {
const needle = normalize(query);
return PROJECTS.filter(project => (!statuses.length || statuses.includes(project.status)) &&
(!needle || normalize(`${project.label} ${project.description}`).includes(needle)));
}
export function projectById(id: string): Project | null { return PROJECTS.find(project => project.id === id) ?? null; }
Encuentra el próximo proyecto
Busca y filtra una lista nativa, selecciona un proyecto y consulta sus detalles. Un resultado vacío oculta el detalle anterior.
Filter Bar · Virtual List · Card · Badge
Capturas del dispositivo en inglés.


import { Component, computed, signal } from '@angular/core';
import { Text, View } from '@ng-native/components';
import { NeuBadgeComponent, NeuCardComponent, NeuFilterBarComponent, NeuVirtualListComponent, type NeuOption } from '@neural-ui/native';
import { filterProjects, projectById } from './logic.js';
@Component({
imports: [Text, View, NeuBadgeComponent, NeuCardComponent, NeuFilterBarComponent, NeuVirtualListComponent],
template: `
<neu-filter-bar label="Project status" searchLabel="Search projects" [options]="statuses"
[(query)]="query" [(value)]="filters" clearLabel="Clear filters" />
<view class="count"><neu-badge [label]="countLabel()" /></view>
<neu-virtual-list [items]="projects()" [height]="240" emptyLabel="No projects match these filters" (selected)="select($event)" />
@if (selectedProject(); as project) {
<neu-card [title]="project.label" description="Project details">
<neu-badge [label]="statusLabel(project.status)" />
<text class="body">{{ project.description }}</text>
</neu-card>
}
`,
styles: `:host { gap: 16px; } .count { align-items: flex-start; } .body { color: var(--neu-text, #172033); font-size: 16px; }`,
})
export class CollectionsRecipe {
readonly statuses: readonly NeuOption[] = [{ id: 'active', label: 'In progress' }, { id: 'review', label: 'Review' }, { id: 'done', label: 'Done' }];
readonly query = signal('');
readonly filters = signal<readonly string[]>([]);
readonly selected = signal('native');
readonly projects = computed(() => filterProjects(this.query(), this.filters()));
readonly countLabel = computed(() => this.projects().length === 1 ? '1 project' : `${this.projects().length} projects`);
// Filtering hides a stale detail; selecting a visible row establishes the next detail explicitly.
readonly selectedProject = computed(() => this.projects().some(project => project.id === this.selected()) ? projectById(this.selected()) : null);
select(id: string): void { if (this.projects().some(project => project.id === id)) this.selected.set(id); }
statusLabel(status: string): string { return this.statuses.find(option => option.id === status)?.label ?? status; }
}
export interface Project { readonly id: string; readonly label: string; readonly description: string; readonly status: 'active' | 'review' | 'done'; }
export const PROJECTS: readonly Project[] = [
{ id: 'native', label: 'Neural Launch', description: 'A mobile experience for your team', status: 'active' },
{ id: 'studio', label: 'Design studio', description: 'Ideas ready for the next review', status: 'review' },
{ id: 'guide', label: 'Welcome guide', description: 'The first step for every new member', status: 'done' },
];
const normalize = (value: string) => value.trim().normalize('NFD').replace(/\p{Diacritic}/gu, '').toLocaleLowerCase('en');
export function validEmail(email: string): boolean { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim()); }
export function validProfile(name: string, email: string, team: string): boolean {
return name.trim().length >= 2 && validEmail(email) && ['design', 'product', 'engineering'].includes(team);
}
export function filterProjects(query: string, statuses: readonly string[]): readonly Project[] {
const needle = normalize(query);
return PROJECTS.filter(project => (!statuses.length || statuses.includes(project.status)) &&
(!needle || normalize(`${project.label} ${project.description}`).includes(needle)));
}
export function projectById(id: string): Project | null { return PROJECTS.find(project => project.id === id) ?? null; }
Navega, edita y guarda
Cambia de sección, edita un proyecto en una hoja nativa, confirma el borrador y muestra el resultado.
Nav · Card · Badge · Input · Bottom Sheet · Button · Toast · Timeline · Switch
Capturas del dispositivo en inglés.


import { Component, signal } from '@angular/core';
import { Text } from '@ng-native/components';
import { NeuBadgeComponent, NeuBottomSheetComponent, NeuButtonComponent, NeuCardComponent,
NeuInputComponent, NeuNavComponent, NeuSwitchComponent, NeuTimelineComponent, NeuToastComponent,
type NeuOption } from '@neural-ui/native';
@Component({
imports: [Text, NeuBadgeComponent, NeuBottomSheetComponent, NeuButtonComponent, NeuCardComponent,
NeuInputComponent, NeuNavComponent, NeuSwitchComponent, NeuTimelineComponent, NeuToastComponent],
template: `
<neu-nav [items]="navigation" [(value)]="tab" />
@switch (tab()) {
@case ('home') {
<neu-card [title]="title()" description="A shared idea. A team ready to build.">
<neu-badge label="In progress" />
<text class="body">Organize the next launch and keep details up to date.</text>
<neu-button label="Edit project" (pressed)="beginEdit()" />
</neu-card>
}
@case ('activity') {
<neu-card title="Team activity"><neu-timeline [items]="activity" /></neu-card>
}
@case ('settings') {
<neu-card title="Preferences"><neu-switch label="Project notifications" [(checked)]="notifications" /></neu-card>
}
}
<neu-bottom-sheet title="Edit project" [(open)]="editing" closeLabel="Close editor">
<neu-input label="Project name" [(value)]="draft" [invalid]="!draft().trim()" error="Enter a name" />
<neu-button label="Save project" [disabled]="!draft().trim()" (pressed)="save()" />
</neu-bottom-sheet>
<neu-toast message="Project updated" [(open)]="saved" [duration]="0" closeLabel="Dismiss notification" />
`,
styles: `.body { color: var(--neu-text, #172033); font-size: 16px; }`,
})
export class ActionsRecipe {
readonly navigation: readonly NeuOption[] = [{ id: 'home', label: 'Home' }, { id: 'activity', label: 'Activity' }, { id: 'settings', label: 'Settings' }];
readonly activity = [{ id: 'created', label: 'Project created', date: 'Today', done: true }, { id: 'review', label: 'Next review', date: 'Tomorrow' }];
readonly tab = signal('home');
readonly title = signal('Mobile launch');
readonly draft = signal('');
readonly notifications = signal(true);
readonly editing = signal(false);
readonly saved = signal(false);
beginEdit(): void { this.draft.set(this.title()); this.saved.set(false); this.editing.set(true); }
save(): void {
const title = this.draft().trim();
if (!title) return;
this.title.set(title);
this.editing.set(false);
this.saved.set(true);
}
}