E-commerceecommerceshopcategoriesgridnavigation
Category Tiles
Сетка категорий с иллюстрациями, количеством товаров и анимированной стрелкой.
Живое превью
Использование
Скопируйте и вставьте этот код в ваш 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 },
]} />Ручная установка
Если вы не используете пакет npm, создайте файл и вставьте следующий код: 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; }
}Краткая информация
- Категория
- E-commerce
- Имя файла
CategoryTiles.tsx- Зависимости
- Нет — чистый React + CSS
- Теги
- ecommerceshopcategoriesgridnavigation