Estudio de caso de experiencia de usuario de una página de descripción general

Cómo diseñé la página de descripción general para todas las categorías de publicaciones

Objetivos de UI y UX

Mi objetivo principal con una página de descripción general que muestra todas las categorías de publicaciones publicadas era aumentar la visibilidad de las categorías que realmente están disponibles. También vi este cambio como una oportunidad para mejorar la belleza visual de la página, así como para alinear la UX con las otras páginas en esta aplicación web progresiva.

La siguiente captura de pantalla muestra cómo se veía la página antes del rediseño.

Image e8edca7d7ea0

Que tenia que cambiar

La implementación anterior tenía dos problemas importantes. En primer lugar, el diseño no era válido, ya que las categorías con menos de 5 elementos aumentaron las imágenes de portada posteriores a la vista previa disponibles para ajustarse a todo el ancho. En segundo lugar, no había una barra de acción principal visible en la parte superior de la página; definitivamente debería haber una renderizada, ya que proporciona elementos de navegación principales al usuario.

Además, resulta evidente que la página de descripción general de las categorías no hizo un gran trabajo al hacerlas visibles. En cambio, el usuario vio principalmente imágenes de portada para publicaciones relacionadas con cada categoría. Solo al echar un segundo vistazo, se reconoce la fila con un icono y el título de cada categoría. Además de ser estéticamente poco agradable, esta interfaz de usuario simplemente estaba mal diseñada.

Corrigiendo errores

Como esta es mi aplicación web personal, también fui yo quien estropeó las cosas inicialmente en la página de descripción general de categorías. Sin más excusas, me senté a reelaborar toda la página.

La nueva versión muestra una sola columna de filas, donde cada elemento muestra una imagen de vista previa grande para una categoría. La imagen personalizada toma todo el ancho permitido. Centrado en ambos ejes está el título de la categoría en el medio, lo que lo hace visible y reconocible de inmediato. Para mejorar la legibilidad, agregué un degradado sutil de negro a transparente debajo del texto.

Al pasar el cursor sobre la imagen y el texto, aparece un texto de información adicional que muestra el número total de publicaciones para esta categoría, una información que no estaba disponible anteriormente.

Para solucionar el problema de navegación, también reutilicé la barra de acción existente con todas las rutas principales disponibles para el usuario.

Image 0616bdd11b98

Mejorando a través de la iteración

Para proporcionar una experiencia de usuario más agradable, también agregué una sutil animación de escala a la imagen al pasar el cursor sobre una fila de categoría. A medida que la imagen se escala dentro de sus límites originales, la vista no crece innecesariamente.

Además, una nueva fila debajo de la imagen de la categoría se hace visible a través de la animación al pasar el cursor sobre ella. Esta fila muestra una selección de las últimas publicaciones de la categoría. Cada vista previa posterior es un enlace a la publicación. Todos los demás elementos interactivos de la fila de categoría abren la página de detalles de la categoría.

Para dar la ilusión de más contenido del que realmente hay, utilicé la clase de "pila" de Daisy-UIs para dar a cada vista previa una pila falsa para connotar una impresión de más elementos de vista previa debajo de los actuales.

Image 41c052a8c928

En dispositivos pequeños, no se procesan vistas previas. Un usuario solo puede hacer clic en la imagen de categoría grande para ir a la página de detalles, donde se enumeran todas las publicaciones.

Revisión y perspectiva

Como puede ver, incluso una página simple que solo tiene que proporcionar una descripción general de los elementos puede ser una fuente de errores tanto visuales como conceptuales, así como material de aprendizaje sobre cómo diseñar una interfaz de usuario buena y funcional, así como una experiencia de usuario.

No agregué ninguna búsqueda de texto local para que los usuarios filtraran por entrada de texto. Por ahora, tengo la intención de promover la exploración en la página desplazándome y desplazándome. Si esto se convierte en un problema en el futuro (por ejemplo, porque los usuarios no pueden encontrar el contenido que buscan), agregaré algunas opciones de filtrado. Esto también podría ser necesario a medida que aumenta el número de publicaciones y categorías. Por supuesto, le invito a que pruebe la página usted mismo.

El código fuente

El siguiente texto contiene el código completo y sin modificar tanto para el contenedor de la página como para los grupos de categorías. Todo está escrito en Typecript con React.js y Tailwind.css. Si cree que puede reutilizar algunas piezas, me alegro de poder ayudarlo.

//
// Page container.
//

import React from "react";
import { CMSCategoryResolution } from "../../cms/entities/cms.entity.catgory";
import Reveal from "../Reveal/Reveal";
import { BogPostCategoryPostsGroup } from "./BlogPostCategoryPostsGroup";
import BlogPostCategoriesOverviewHero from "./BlogPostCategoriesOverviewHero";
import PrimaryActionBar from "../PrimaryActionsBar/PrimaryActionsBar";

/*
 *
 * Interfaces.
 *
 */

interface Props {
  categories: CMSCategoryResolution[];
}

/*
 *
 * Components.
 *
 */

export default function BlogPostCategoriesOverview(props: Props) {
  const { categories } = props;

  return (
    <div className="max-w-6xl mx-auto">
      <BlogPostCategoriesOverviewHero />
      <div className="mt-10 mb-20">
        <PrimaryActionBar isCentered color="white" />
      </div>
      <div className="px-6 mx-auto space-y-6 lg:px-0 md:space-y-16 lg:space-y-20">
        {categories.map((c, i) => (
          <Reveal key={i}>
            <BogPostCategoryPostsGroup category={c} maxPostsEachThreshold={20} />
          </Reveal>
        ))}
      </div>
    </div>
  );
}

//
// BogPostCategoryPostsGroup.
//

import React from "react";
import { CMSCategoryResolution } from "../../cms/entities/cms.entity.catgory";
import { FLPath } from "../../models/route/model.route";
import { CMSCategoryIconFactory } from "../CMS/CMSCategoryIcon";
import Link from "next/link";
import RiMore from "remixicon-react/MoreLineIcon";

/*
 *
 * Interfaces.
 *
 */

interface Props {
  category: CMSCategoryResolution;
  maxPostsEachThreshold: number;
}

/*
 *
 * Components.
 *
 */

export const BogPostCategoryPostsGroup = (props: Props) => {
  const { category, maxPostsEachThreshold } = props;
  const { posts } = category;

  if (posts.length === 0) {
    return null;
  }

  const url = `${FLPath.Categories}/${category.slug.current}`;
  const Icon = CMSCategoryIconFactory({ variant: category.slug.current });

  return (
    <div className="flex flex-col items-center group">
      <div className="relative max-w-4xl overflow-visible rounded-md">
        <div className="absolute items-end justify-start hidden w-full h-full px-6 space-x-4 overflow-x-scroll duration-500 ease-in-out translate-y-12 opacity-0 md:flex pb-14 group-hover:opacity-100 group-hover:translate-y-20">
          {posts.slice(0, 4).map((p, i) => (
            <span className="stack">
              <Link
                href={`${FLPath.Posts}/${p.slug.current}`}
                aria-label={p.title}
                prefetch={false}>
                <a className="duration-200 ease-out hover:scale-105">
                  <img
                    src={p.imageUrl.jpg}
                    alt={p.title}
                    loading="lazy"
                    className="object-contain h-20 rounded "
                  />
                </a>
              </Link>

              {Array.from({ length: 2 }, (_, i) => (
                <span key={i} style={{ width: 152, height: 80 }} className="bg-gray-800 rounded" />
              ))}
            </span>
          ))}
          {posts.length >= 5 && (
            <Link passHref href={url} aria-label={`Link to ${category.title}`} prefetch={false}>
              <a className="flex flex-col items-center justify-center h-20 w-14">
                <RiMore />
              </a>
            </Link>
          )}
        </div>

        <div className="relative flex flex-col items-center overflow-hidden duration-500 ease-in-out rounded-md shadow-xl md:group-hover:-translate-y-20">
          <img
            className="object-cover w-full duration-500 ease-in-out group-hover:scale-105"
            width={960}
            height={200}
            src={category.imageUrl.webp}
            alt={category.title}
            loading="lazy"
          />
          <Link passHref href={url} aria-label={`Link to ${category.title}`} prefetch={false}>
            <a className="absolute flex flex-col items-center justify-center w-full h-full bg-gradient-to-t from-primary">
              <span className="flex items-center space-x-2 duration-300 ease-in-out translate-y-2 group-hover:-translate-y-3">
                <Icon size="2em" />
                <h2 className="text-sm md:text-xl">{category.title}</h2>
              </span>
              <span className="text-sm font-light text-gray-200 transition duration-500 ease-in-out translate-y-2 opacity-0 group-hover:opacity-100 group-hover:-translate-y-2">
                {category.posts.length}
                {category.posts.length === maxPostsEachThreshold ? "+" : ""}{" "}
                {category.posts.length === 1 ? "post" : "posts"}
              </span>
            </a>
          </Link>
        </div>
      </div>
    </div>
  );
};