Uso de la API DModal para renderizar ventanas modales (también llamadas ventanas emergentes o diálogos) en Discourse

Discourse 3.1.0.beta6 incluye una nueva API basada en el componente <DModal>. DModal forma parte del kit de interfaz de usuario y se importa desde discourse/ui-kit/d-modal.

:information_source: Esto reemplaza a la antigua API basada en controladores, que ahora está deprecada. Si tienes modales existentes que usan las APIs antiguas, consulta la guía de migración aquí.

Renderizado de un modal

Los modales se renderizan incluyendo el componente <DModal> en una plantilla de handlebars. Si aún no tienes una plantilla adecuada, consulta Using Plugin Outlet Connectors from a Theme or Plugin.

Un modal simple se vería así:

<DButton
  @translatedLabel="Show Modal"
  @action={{fn (mut this.modalIsVisible) true}}
/>

{{#if this.modalIsVisible}}
  <DModal @title="My Modal" @closeModal={{fn (mut this.modalIsVisible) false}}>
    Hello world, this is some content in a modal
  </DModal>
{{/if}}

:information_source: El helper mut se utiliza aquí como una forma exclusiva de hbs para establecer un valor. También podrías establecer modalIsVisible usando cualquier otro método estándar de Ember.

Este ejemplo creará un modal simple como este:

Envolverlo en un componente

Antes de introducir más complejidad, generalmente es mejor envolver tu nuevo modal en su propia definición de Componente. Moveremos el contenido de <DModal> dentro de un nuevo componente <MyModal />.

// components/my-modal.gjs
<template>
  <DModal @title="My Modal" @closeModal={{@closeModal}}>
    Hello world, this is some content in a modal
  </DModal>
</template>

Actualizar este archivo .gjs a un componente basado en clase te permitirá introducir lógica y estado más complejos.

Para hacer uso del nuevo componente, actualiza el punto de llamada para que lo referencie, asegurándote de pasar un argumento @closeModal.

<DButton
  @translatedLabel="Show Modal"
  @action={{fn (mut this.modalIsVisible) true}}
/>

{{#if this.modalIsVisible}}
  <MyModal @closeModal={{fn (mut this.modalIsVisible) false}} />
{{/if}}

Agregar un pie de página

Muchos modales tienen algún tipo de llamada a la acción. En Discourse, estas suelen estar ubicadas en la parte inferior del modal. Para hacer esto posible, DModal tiene una serie de ‘bloques nombrados’ en los que se puede renderizar contenido. Aquí tienes el ejemplo actualizado para incluir dos botones en el pie de página, uno de los cuales es nuestro botón estándar DModalCancel.

<DModal @title="My Modal" @closeModal={{@closeModal}}>
  <:body>
    Hello world, this is some content in a modal
  </:body>
  <:footer>
    <DButton class="btn-primary" @translatedLabel="Submit" />
    <DModalCancel @close={{@closeModal}} />
  </:footer>
</DModal>

Renderizado de un modal desde un contexto no-hbs

Idealmente, las instancias de <DModal> deberían renderizarse desde dentro de una plantilla de Ember utilizando la técnica declarativa demostrada anteriormente. Si eso no es viable para tu caso de uso, se puede hacer inyectando el servicio modal y llamando a modal.show().

Asegúrate de haber envuelto tu modal en su propio componente como se describió anteriormente. Luego, activa el modal pasando una referencia de tu clase de componente a showModal:

import MyModal from "discourse/components/my-modal";

// (inyecta el servicio modal en el lugar correspondiente)

// Agrega esta llamada cada vez que quieras abrir el modal.
// Se pasará un argumento `@closeModal` a tu componente automáticamente.
this.modal.show(MyModal);

// Opcionalmente, pasa un parámetro '`model`'. Se pasa como `@model` a tu componente.
// Esto puede incluir datos, y también acciones/callbacks para que tu Modal los use.
this.modal.show(MyModal, {
  model: { topic: this.topic, someAction: this.someAction },
});

// `modal.show()` devuelve una promesa, por lo que puedes esperar a que se cierre.
// Se resolverá con los datos pasados a la acción `@closeModal`.
const result = await this.modal.show(MyModal);

¡Más personalizabilidad!

<DModal> tiene una serie de bloques y argumentos nombrados.

Argumentos

Arg Propósito
@closeModal Requerido para que aparezca la interfaz de cierre.
@title Renderiza <h1 id="discourse-modal-title">; conecta aria-labelledby.
@subtitle Texto pequeño debajo del título.
@flash / @flashType Alerta en línea en la parte superior del modal (DFlashMessage).
@hideHeader, @hideFooter Oculta regiones completas.
@headerClass, @bodyClass Clase adicional en los contenedores de encabezado/cuerpo.
@dismissable Verdadero por defecto si se establece @closeModal. Deshabilita Esc / clic en el fondo / X.
@autofocus Verdadero por defecto. Enfoque automático del primer elemento enfocable mediante dTrapTab.
@submitOnEnter Verdadero por defecto. Enter hace clic en .d-modal__footer .btn-primary a menos que el foco esté en un formulario / textarea / select-kit.
@beforeClose async ({ initiatedBy }) => boolean. Devuelve false para cancelar el cierre (p. ej., confirmación de formulario sucio).
@hidden Pausa el manejo del teclado; se usa cuando hay un modal anidado encima.
@tagName "div" (predeterminado) o "form". Usa "form" para formularios para que el envío nativo funcione.

Bloques

Block Posición Cuándo usarlo
default / :body Área de contenido principal Área predeterminada
:aboveHeader Muy arriba, antes del encabezado Raramente necesario; para contenido que debe estar por encima de la barra de título (p. ej., un banner).
:headerAboveTitle Dentro del encabezado, antes del título Presente pero sin usar. Raramente necesario.
:belowModalTitle Dentro de .d-modal__title, después del <h1> Excelente posición para información meta complementaria.
:headerBelowTitle Dentro del encabezado, después del bloque de título Pestañas, subnavegación o campo de búsqueda que forma parte del encabezado.
:headerPrimaryAction Lado derecho del encabezado solo en móvil Reemplaza el botón de cierre X con una acción principal (p. ej., “Guardar”). También renderiza automáticamente un botón “Cancelar” a la izquierda y agrega .--has-primary-action al encabezado.
:belowHeader Entre el encabezado y el cuerpo Contenido de subencabezado persistente (p. ej., búsqueda) que está fuera del cuerpo desplazable, para una visualización fija.
:aboveFooter Entre el cuerpo y el pie de página Suprimido cuando se establece @hideFooter. Úsalo para contenido vinculado al pie de página pero fuera de él. También es raro.
:footer Barra de acciones inferior Botones primarios y secundarios. El primer .btn-primary aquí es lo que activa Enter.
:belowFooter Después del pie de página Raramente necesario; ignora @hideFooter. Útil para texto de estado fuera del área del pie de página con borde.

Fuentes: la guía de estilos interactiva para los argumentos, y la implementación de la plantilla d-modal para los bloques nombrados.

CSS

Usa las clases .d-modal como ancla para sobrescribir el núcleo y evita el selector heredado .modal.

4 modificadores disponibles:

  • .--large establece el ancho máximo en 800px (solo escritorio)
  • .--max establece el ancho máximo en 90vw (solo escritorio)
  • .has-search establece la altura fija (80vh): destinado a modales con sistema de búsqueda/filtrado para evitar cambios de altura basados en la longitud del resultado (solo escritorio)
  • .--stacked establece los botones del pie de página en apilamiento (solo móvil)

Este documento está bajo control de versiones - sugiere cambios en github.

17 Me gusta

Se dividió una publicación en un nuevo tema: ¿Puedo mostrar una ventana modal desde head_tag