Saltar al contenido principal
Neural Nativev0.1.0-beta.1Documentación

Importación

component.ts
import { NeuDataViewComponent } from '@neural-ui/native/data-view';

Ejemplos

Importa ExampleComponent en su padre y coloca <app-example /> dentro del neu-theme raíz de tu aplicación. El preview aporta el marco y las zonas seguras; utiliza la estructura raíz de Instalación en tu app.

Básico

Utiliza una plantilla Angular para cada fila. Este listado de personas combina Avatar y texto; Data View conserva la búsqueda, el orden y la selección.

Ver y copiar el ejemplo completo
basic.component.ts
import { Component, signal, computed } from '@angular/core';
import { NeuAvatarComponent } from '@neural-ui/native/avatar';
import { NeuDataViewComponent } from '@neural-ui/native/data-view';
import { View, Text } from '@ng-native/components';
import type { NeuOption } from '@neural-ui/native';

@Component({
  selector: 'app-example',
  imports: [NeuAvatarComponent, NeuDataViewComponent, View, Text],
  template: `
    <view class="people-header">
      <view class="people-heading">
        <text class="people-title">Miembros del equipo</text>
        <text class="people-subtitle">Espacio del producto</text>
      </view>
      <view class="people-count"><text class="people-count-label">{{ people().length }}</text></view>
    </view>
    <ng-template #person let-item>
      <view class="person-row" [class.person-row-selected]="selected() === item.id">
        <neu-avatar [label]="item.label" size="md" />
        <view class="person-copy">
          <text class="person-name">{{ item.label }}</text>
          <text class="person-description">{{ item.description }}</text>
        </view>
        <view class="person-end" [accessibilityElementsHidden]="true" importantForAccessibility="no-hide-descendants">
          @if (selected() === item.id) {
            <view class="person-selected-mark"><text class="person-check">✓</text></view>
          } @else {
            <view class="person-chevron" />
          }
        </view>
      </view>
    </ng-template>
    <neu-data-view emptyLabel="No hay resultados" refreshLabel="Actualizar" sortLabel="Invertir orden" [items]="people()" [itemTemplate]="person" searchLabel="Buscar personas" [(query)]="query" [(descending)]="descending" (selected)="selected.set($event)" (refresh)="refreshPeople()" />
    <view class="people-footer">
      <text class="people-status" accessibilityLiveRegion="polite">{{ selected() ? selectedName() + ' · Selección actual' : 'Selecciona a una persona' }}</text>
      @if (refreshed()) { <text class="people-updated" accessibilityLiveRegion="polite">Actualizado</text> }
    </view>
  `,
  styles: `
.actions { gap: 12px; align-items: flex-start; } text { color: var(--neu-text, #172033); font-size: 16px; }

.people-header { flex-direction: row; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
.people-heading { flex: 1; min-width: 0; gap: 4px; }
.people-title { font-size: 22px; font-weight: 700; }
.people-subtitle { font-size: 13px; color: var(--neu-muted, #596477); }
.people-count { min-width: 28px; padding: 4px 9px; align-items: center; border-radius: 999px; background-color: var(--neu-tonal, #eff6ff); }
.people-count-label { font-size: 12px; font-weight: 600; color: var(--neu-accent, #006ee6); }
.person-row { flex-direction: row; align-items: center; gap: 12px; padding: 12px; border-width: 1px; border-color: var(--neu-border, #a5afbf); border-radius: 12px; background-color: var(--neu-surface, #ffffff); box-shadow: 0 2px 8px rgba(15, 23, 42, 0.04); transition: background-color 180ms ease-out, border-color 180ms ease-out; }
@media (prefers-reduced-motion: reduce) { .person-row { transition: none; } }
.person-row-selected { border-color: var(--neu-accent, #006ee6); background-color: var(--neu-tonal, #eff6ff); }
.person-copy { flex: 1; min-width: 0; gap: 4px; }
.person-name { font-size: 15px; font-weight: 600; }
.person-description { font-size: 13px; color: var(--neu-muted, #596477); }
.person-end { width: 20px; height: 24px; align-items: center; justify-content: center; }
.person-chevron { width: 6px; height: 6px; border-top-width: 1.5px; border-right-width: 1.5px; border-color: var(--neu-muted, #596477); transform: rotate(45deg); opacity: 0.6; }
.person-selected-mark { width: 20px; height: 20px; align-items: center; justify-content: center; border-radius: 10px; background-color: var(--neu-accent, #006ee6); }
.person-check { font-size: 12px; font-weight: 600; color: var(--neu-on-accent, #ffffff); }
.people-footer { flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px; min-height: 20px; margin-top: 12px; }
.people-status { font-size: 12px; color: var(--neu-muted, #596477); }
.people-updated { font-size: 12px; color: var(--neu-accent, #006ee6); }

  `,
})
export class ExampleComponent {
  readonly initialPeople: readonly NeuOption[] = [
    {
      "id": "ada",
      "label": "Ada Lovelace",
      "description": "Diseñadora de producto"
    },
    {
      "id": "grace",
      "label": "Grace Hopper",
      "description": "Ingeniería móvil"
    },
    {
      "id": "linus",
      "label": "Linus Torvalds",
      "description": "Ingeniero de plataforma"
    }
  ];
  readonly people = signal(this.initialPeople);
  readonly query = signal('');
  readonly descending = signal(false);
  readonly selected = signal('');
  readonly refreshed = signal(false);
  readonly selectedName = computed(() => this.people().find(person => person.id === this.selected())?.label ?? 'Todavía ninguno');
  refreshPeople(): void {
    this.people.set([...this.initialPeople]);
    this.refreshed.set(true);
  }
}

Plantilla de elemento declarativa

Utiliza la plantilla declarativa tipada como alternativa a itemTemplate. Su índice sigue la lista filtrada y ordenada.

Ver y copiar el ejemplo completo
declarative-item-template.component.ts
import { Component, signal } from '@angular/core';
import { NeuAvatarComponent } from '@neural-ui/native/avatar';
import { NeuDataViewComponent } from '@neural-ui/native/data-view';
import { View, Text, ScrollView } from '@ng-native/components';
import { NeuDataViewItemDirective } from '@neural-ui/native/data-view';

@Component({
  selector: 'app-example',
  imports: [NeuAvatarComponent, NeuDataViewComponent, NeuDataViewItemDirective, View, Text, ScrollView],
  template: `
    <scroll-view class="usage-scroll" keyboardShouldPersistTaps="handled"><view class="usage-stack">
    <neu-data-view emptyLabel="No hay resultados" refreshLabel="Actualizar" sortLabel="Invertir orden" [items]="people" searchLabel="Buscar personas" (selected)="selected.set($event)">
      <ng-template neuDataViewItem let-person let-index="index">
        <view class="member-row"><neu-avatar [label]="person.label" size="md" /><view class="member-copy"><text class="usage-heading">{{ person.label }}</text><text class="usage-caption">{{ person.description }}</text></view><text class="usage-caption">{{ index + 1 }}</text></view>
      </ng-template>
    </neu-data-view>
    <text class="usage-caption" accessibilityLiveRegion="polite">{{ selected() ? 'Persona seleccionada: ' + selected() : 'Selecciona a una persona' }}</text>
    </view></scroll-view>
  `,
  styles: `
.actions { gap: 12px; align-items: flex-start; } text { color: var(--neu-text, #172033); font-size: 16px; }

.usage-scroll { flex: 1; }
.usage-stack { gap: 20px; padding-bottom: 20px; }
.usage-section { gap: 12px; }
.usage-heading { color: var(--neu-text, #172033); font-size: 16px; font-weight: 600; }
.usage-caption { color: var(--neu-muted, #64748b); font-size: 14px; }
.usage-row { flex-direction: row; flex-wrap: wrap; gap: 12px; align-items: center; }
.usage-actions { flex-direction: row; flex-wrap: wrap; gap: 8px; align-items: center; }

.member-row { flex-direction: row; align-items: center; gap: 12px; padding: 12px; border-radius: 12px; border-width: 1px; border-color: var(--neu-border, #a5afbf); background-color: var(--neu-surface, #ffffff); } .member-copy { flex: 1; min-width: 0; gap: 4px; }
  `,
})
export class ExampleComponent {
  readonly people = [{ id: 'alex', label: 'Alex Morgan', description: 'Diseñadora de producto' }, { id: 'sam', label: 'Sam Rivera', description: 'Desarrollador móvil' }];
  readonly selected = signal('');
}
Playground interactivo

Ajusta la configuración y prueba el resultado.

Capturas del dispositivo en inglés.

Data View · android · native capture
AndroidCaptura nativa
Data View · ios · native capture
iOSCaptura nativa

Personalización

Importa estas directivas junto con el componente y añádelas a Component.imports. Los ejemplos anteriores incluyen la configuración completa.

personalization-imports.ts
import { NeuDataViewItemDirective } from '@neural-ui/native/data-view';
Plantilla o regiónContextoUso
ng-template[neuDataViewItem]$implicit: NeuOption; index: numberSustituye el contenido visual de cada fila. La fila conserva el filtrado, la ordenación, el estado desactivado y el evento selected. index sigue la lista filtrada y ordenada.

Contextos de las plantillas

template-contexts.ts
import type { NeuDataViewItemContext } from '@neural-ui/native/data-view';

let-item recibe $implicit; los campos con nombre utilizan let-index="index". Usa los tipos exportados al declarar un TemplateRef reutilizable. Mantén el contenido interno como presentación: el padre conserva la acción accesible y el comportamiento de selección.

Plantillas reutilizables

También puedes declarar <ng-template #row let-item> y enlazar [itemTemplate]="row". Un itemTemplate explícito tiene prioridad sobre la directiva proyectada; si no aportas ninguno, se muestra el contenido predeterminado.

Referencia API

Inputs

NombreTipoPredeterminadoEnlaceDescripción
itemsreadonly NeuOption[][]InputDatos ordenados del componente. Utiliza el contrato siguiente e identificadores estables y únicos.
itemTemplateTemplateRef<NeuDataViewItemContext> | undefinedundefinedInputPlantilla Angular para cada fila visible. let-item recibe el elemento original; let-index="index" recibe su posición tras filtrar y ordenar. La plantilla controla el contenido visual; Data View conserva la selección y el estado disabled. Sin plantilla se utiliza la tarjeta de texto predeterminada.
loadingbooleanfalseInputSustituye la lista de resultados por un skeleton y desactiva la actualización mientras la aplicación carga los datos. La búsqueda y la ordenación siguen disponibles.
errorstring''InputSustituye los resultados por una alerta con este mensaje cuando loading es false. Borra el mensaje para volver a mostrar los resultados.
searchLabelstring'Search items'InputTexto o nombre accesible utilizado para buscar. No ejecuta la acción por sí solo.
sortLabelstring'Reverse order'InputNombre accesible de la acción con icono que invierte el orden de la lista. La acción se alinea a la derecha de la barra.
refreshLabelstring'Refresh'InputNombre accesible del icono de actualización. Emite refresh para que tu aplicación recargue los datos y se desactiva mientras loading está activo.
emptyLabelstring'No items'InputTexto o nombre accesible utilizado para estado vacío. No ejecuta la acción por sí solo.

Models

NombreTipoPredeterminadoEnlaceDescripción
querystring''Model · bidireccionalTexto de búsqueda actual utilizado por el control. Enlaza una signal escribible mediante [(query)].
descendingbooleanfalseModel · bidireccionalIndica si la dirección de orden solicitada es descendente. Enlaza una signal escribible mediante [(descending)].

Outputs

NombrePayloadCuándo se emite
queryChangestringEmite el valor actualizado de query. [(query)] enlaza este output junto con su input model.
descendingChangebooleanEmite el valor actualizado de descending. [(descending)] enlaza este output junto con su input model.
selectedstringEmite el ID del elemento seleccionado.
refreshvoidSolicita actualizar; carga los datos nuevos en tu aplicación.

Estado de los models

Enlaza la signal con [(nombreModel)] sin llamarla. Utiliza nombreModel() para leer su valor.

model-fields.ts
import { signal } from '@angular/core';

// Fields inside your component class
readonly query = signal<string>('');
readonly descending = signal<boolean>(false);

Tipos e interfaces de datos

Utiliza los imports en tu aplicación. Las definiciones siguientes se muestran como referencia.

NeuOption

NeuOption.ts
import type { NeuOption } from '@neural-ui/native';

// Public definition for reference only. Import this type; do not redeclare it.
export interface NeuOption {
    readonly id: string;
    readonly label: string;
    readonly description?: string;
    readonly disabled?: boolean;
}
CampoTipoRequisitoSignificado
idstringObligatorioIdentificador estable y único para seguimiento y selección.
labelstringObligatorioNombre legible del elemento.
descriptionstringOpcionalTexto de apoyo opcional; su presentación depende del control.
disabledbooleanOpcionalImpide activar o seleccionar este elemento.

NeuDataViewItemContext

NeuDataViewItemContext.ts
import type { NeuDataViewItemContext } from '@neural-ui/native/data-view';
import type { NeuOption } from '@neural-ui/native';

// Public definition for reference only. Import this type; do not redeclare it.
export interface NeuDataViewItemContext {
    readonly $implicit: NeuOption;
    /** Position in the currently filtered and sorted collection. */
    readonly index: number;
}
CampoTipoRequisitoSignificado
$implicitNeuOptionObligatorioElemento original suministrado a la plantilla de fila; se recibe mediante let-item.
indexnumberObligatorioPosición desde cero en la colección visible; se recibe mediante let-index="index". La sección de personalización explica cómo aplica este componente el filtrado, la ordenación o el reciclado de filas.