1 estrella en GitHub = 1 componente nuevo.

Dar una estrella
E-commerceecommerceshopcategoriesgridnavigation

Category Tiles

Cuadrícula de categorías con ilustraciones, número de productos y flecha animada.

Vista Previa en Vivo

Uso

Copia y pega este código directamente en tu componente React:

tsx
import { CategoryTiles } from 'react-component-kit-ui/ecommerce';

<CategoryTiles categories={[
  { id: 'audio', name: 'Audio', href: '/shop/?category=audio', count: 18, icon: 'headphones', hue: 38 },
  { id: 'bags', name: 'Bags', href: '/shop/?category=bags', count: 9, icon: 'backpack', hue: 150 },
]} />

Instalación Manual

Si no estás usando el paquete npm, crea un archivo nuevo y pega el siguiente código: src/components/CategoryTiles.tsx + CategoryTiles.css:

CategoryTiles.tsx
'use client';

import type { CSSProperties } from 'react';
import ProductMedia from './ProductMedia';
import type { StoreCategory } from './types';
import './CategoryTiles.css';

/** class:list → className */
function cx(...args: unknown[]): string {
  const out: string[] = [];
  const walk = (v: unknown) => {
    if (!v) return;
    if (typeof v === 'string' || typeof v === 'number') out.push(String(v));
    else if (Array.isArray(v)) v.forEach(walk);
    else if (typeof v === 'object') for (const [k, on] of Object.entries(v as Record<string, unknown>)) if (on) out.push(k);
  };
  args.forEach(walk);
  return out.join(' ');
}

/** "a: b; --c: d" → style object */
function st(css: string | undefined): CSSProperties {
  const o: Record<string, string> = {};
  for (const decl of String(css ?? '').split(';')) {
    const i = decl.indexOf(':');
    if (i < 0) continue;
    const k = decl.slice(0, i).trim();
    if (k) o[k.startsWith('--') ? k : k.replace(/-([a-z])/g, (_, c) => c.toUpperCase())] = decl.slice(i + 1).trim();
  }
  return o as CSSProperties;
}

type Tile = StoreCategory & { href: string; count?: number };

/**
 * CategoryTiles — Grid of shop categories with illustration, item count and arrow link.
 *
 * @param {string} title - Section heading (optional). Default 'Shop by category'.
 * @param {string} subtitle - Supporting copy (optional).
 * @param {Array<StoreCategory & { href: string, count?: number }>} categories - Tiles to show.
 * @param {number} columns - Desktop columns (2–6). Default 4.
 * @param {string} variant - Tile style (glass|image|minimal). Default 'glass'.
 * @param {string} size - Tile height (sm|md|lg). Default 'md'.
 * @param {string} countLabel - Suffix after the count. Default 'products'.
 * @param {string} className - Extra class.
 *
 * Accessibility: each tile is a single link with a visible name; art is decorative.
 */
export interface CategoryTilesProps {
  title?: string;
  subtitle?: string;
  categories?: Tile[];
  columns?: number;
  variant?: 'glass' | 'image' | 'minimal';
  size?: 'sm' | 'md' | 'lg';
  countLabel?: string;
  className?: string;
}

export function CategoryTiles({
  title = 'Shop by category',
  subtitle,
  categories = [
    { id: 'audio', name: 'Audio', href: '#', count: 18, icon: 'headphones', hue: 38 },
    { id: 'wearables', name: 'Wearables', href: '#', count: 12, icon: 'watch', hue: 200 },
    { id: 'bags', name: 'Bags', href: '#', count: 9, icon: 'backpack', hue: 150 },
    { id: 'home', name: 'Home', href: '#', count: 24, icon: 'lamp', hue: 20 },
  ],
  columns = 4,
  variant = 'glass',
  size = 'md',
  countLabel = 'products',
  className = '',
}: CategoryTilesProps) {
  const cols = Math.min(6, Math.max(2, columns));
  return (
    <>
      <section className={cx(['cat-tiles', `cat-tiles--${variant}`, `cat-tiles--${size}`, className])} style={st(`--ct-cols: ${cols}`)} data-rk-00c0aa="">
        {(title || subtitle) && (
          <header className="cat-tiles__head" data-rk-00c0aa="">
            {title && <h2 className="cat-tiles__title" data-rk-00c0aa="">{title}</h2>}
            {subtitle && <p className="cat-tiles__subtitle" data-rk-00c0aa="">{subtitle}</p>}
          </header>
        )}
        <ul className="cat-tiles__grid" data-rk-00c0aa="">
          {categories.map((c, __i: number) => (
            <li key={__i} data-rk-00c0aa="">
              <a className="cat-tile is-hoverable" href={c.href} data-rk-00c0aa="">
                <ProductMedia icon={c.icon} hue={c.hue} ratio="auto" size="sm" variant={variant === 'minimal' ? 'flat' : 'glow'} className="cat-tile__media" />
                <span className="cat-tile__text" data-rk-00c0aa="">
                  <span className="cat-tile__name" data-rk-00c0aa="">{c.name}</span>
                  {c.count !== undefined && <span className="cat-tile__count" data-rk-00c0aa="">{c.count} {countLabel}</span>}
                </span>
                <span className="cat-tile__arrow" aria-hidden="true" data-rk-00c0aa="">
                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" data-rk-00c0aa=""><path d="M7 17 17 7M8 7h9v9" data-rk-00c0aa="" /></svg>
                </span>
              </a>
            </li>
          ))}
        </ul>
      </section>
    </>
  );
}

export default CategoryTiles;
CategoryTiles.css
/* CategoryTiles — ported from astro-component-kit (ecommerce/CategoryTiles.astro) */

.cat-tiles:where([data-rk-00c0aa]) { container-type: inline-size; --ct-h: 220px; }
.cat-tiles--sm:where([data-rk-00c0aa]) { --ct-h: 160px; }
.cat-tiles--lg:where([data-rk-00c0aa]) { --ct-h: 280px; }
.cat-tiles__head:where([data-rk-00c0aa]) { margin-bottom: var(--sp-6, 1.5rem); }
.cat-tiles__title:where([data-rk-00c0aa]) { margin: 0; font-size: clamp(1.5rem, 3vw, 2rem); font-weight: 800; letter-spacing: -0.03em; color: var(--c-text-1, #fff); }
.cat-tiles__subtitle:where([data-rk-00c0aa]) { margin: var(--sp-2, 0.5rem) 0 0; color: var(--c-text-2, #a3a3a3); }

.cat-tiles__grid:where([data-rk-00c0aa]) {
  display: grid;
  grid-template-columns: repeat(var(--ct-cols), minmax(0, 1fr));
  gap: var(--sp-4, 1rem);
  margin: 0;
  padding: 0;
  list-style: none;
}

.cat-tile:where([data-rk-00c0aa]) {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  height: var(--ct-h);
  padding: var(--sp-5, 1.25rem);
  border-radius: var(--r-lg, 20px);
  border: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.05));
  overflow: hidden;
  color: var(--c-text-1, #fff);
  text-decoration: none;
  isolation: isolate;
  transition: border-color 0.25s ease, transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.cat-tile:hover:where([data-rk-00c0aa]) { border-color: rgba(212, 160, 23, 0.35); transform: translateY(-3px); color: var(--c-text-1, #fff); }
.cat-tile:focus-visible:where([data-rk-00c0aa]) { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 3px; }
.cat-tile:where([data-rk-00c0aa]) .cat-tile__media { position: absolute; inset: 0; height: 100%; z-index: -1; aspect-ratio: auto; }
.cat-tile:where([data-rk-00c0aa]) .cat-tile__media .p-media__art { width: auto; height: 46%; margin-bottom: 18%; }
.cat-tile:where([data-rk-00c0aa])::after { content: ''; position: absolute; inset: 40% 0 0; background: linear-gradient(transparent, rgb(var(--paper-rgb, 5 5 5) / 0.85)); z-index: -1; }
.cat-tiles--minimal:where([data-rk-00c0aa]) .cat-tile:where([data-rk-00c0aa])::after { display: none; }

.cat-tile__text:where([data-rk-00c0aa]) { display: grid; gap: 2px; }
.cat-tile__name:where([data-rk-00c0aa]) { font-size: 1.1rem; font-weight: 800; letter-spacing: -0.01em; }
.cat-tile__count:where([data-rk-00c0aa]) { font-size: 0.8rem; color: var(--c-text-2, #a3a3a3); }
.cat-tile__arrow:where([data-rk-00c0aa]) {
  position: absolute;
  top: var(--sp-4, 1rem);
  right: var(--sp-4, 1rem);
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: rgb(var(--ink-rgb, 255 255 255) / 0.08);
  backdrop-filter: blur(8px);
  transition: background 0.25s ease, color 0.25s ease, transform 0.25s ease;
}
.cat-tile:hover:where([data-rk-00c0aa]) .cat-tile__arrow:where([data-rk-00c0aa]) { background: var(--c-primary-light, #d4a017); color: var(--c-on-primary, #050505); transform: rotate(45deg); }

@container (max-width: 900px) {
  .cat-tiles__grid:where([data-rk-00c0aa]) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@container (max-width: 420px) {
  .cat-tiles__grid:where([data-rk-00c0aa]) { --ct-h: 170px; }
  .cat-tiles__grid:where([data-rk-00c0aa]) { gap: var(--sp-3, 0.75rem); }
  .cat-tile:where([data-rk-00c0aa]) { padding: var(--sp-4, 1rem); }
}
@media (prefers-reduced-motion: reduce) {
  .cat-tile:where([data-rk-00c0aa]), .cat-tile__arrow:where([data-rk-00c0aa]) { transition: none; }
}

Información Rápida

Categoría
E-commerce
Nombre de archivo
CategoryTiles.tsx
Dependencias
Ninguna — React + CSS puro
Etiquetas
ecommerceshopcategoriesgridnavigation