1 звезда на GitHub = 1 новый компонент.

Поставить звезду
E-commerceecommerceshoptoolbarsortcatalog

Product Toolbar

Заголовок каталога с живым счётчиком результатов и сортировкой (ModernSelect), связанной с FilterSidebar.

Живое превью

All products

0 products

Использование

Скопируйте и вставьте этот код в ваш React-компонент:

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

<ProductToolbar title="All products" total={12} target="shop-grid" filtersId="shop-filters" />

Ручная установка

Если вы не используете пакет npm, создайте файл и вставьте следующий код: src/components/ProductToolbar.tsx + ProductToolbar.css:

ProductToolbar.tsx
'use client';

import ModernSelect from '../forms/ModernSelect';
import './ProductToolbar.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(' ');
}

/**
 * ProductToolbar — Catalog heading bar: title, live result count and a sort select
 * (built on ModernSelect) that drives FilterSidebar's sorting.
 *
 * @param {string} title - Page heading. Default 'All products'.
 * @param {string} description - Optional supporting copy.
 * @param {number} total - Initial product count. Default 0.
 * @param {string} target - ProductGrid id. Default 'product-grid'.
 * @param {string} filtersId - FilterSidebar form id (sort select becomes `${filtersId}-sort`). Default 'catalog-filters'.
 * @param {Array<{label: string, value: string}>} sortOptions - Sort choices (values: featured|price-asc|price-desc|rating|name).
 * @param {string} countLabel - Text after the count. Default 'products'.
 * @param {string} headingLevel - Title element (h1|h2). Default 'h1'.
 * @param {string} variant - Layout (split|stacked). Default 'split'.
 * @param {string} className - Extra class.
 *
 * Accessibility: the count lives in a polite live region so filter changes are announced.
 */
export interface ProductToolbarProps {
  title?: string;
  description?: string;
  total?: number;
  target?: string;
  filtersId?: string;
  sortOptions?: Array<{ label: string; value: string }>;
  countLabel?: string;
  headingLevel?: 'h1' | 'h2';
  variant?: 'split' | 'stacked';
  className?: string;
}

export function ProductToolbar({
  title = 'All products',
  description,
  total = 0,
  target = 'product-grid',
  filtersId = 'catalog-filters',
  sortOptions = [
    { label: 'Featured', value: 'featured' },
    { label: 'Price: low to high', value: 'price-asc' },
    { label: 'Price: high to low', value: 'price-desc' },
    { label: 'Top rated', value: 'rating' },
    { label: 'Name A–Z', value: 'name' },
  ],
  countLabel = 'products',
  headingLevel = 'h1',
  variant = 'split',
  className = '',
}: ProductToolbarProps) {
  const Heading: any = headingLevel;
  return (
    <>
      <div className="p-toolbar-cq" data-rk-e742d8="">
      <div className={cx(['p-toolbar', `p-toolbar--${variant}`, className])} data-rk-e742d8="">
        <div className="p-toolbar__text" data-rk-e742d8="">
          <Heading className="p-toolbar__title" data-rk-e742d8="">{title}</Heading>
          {description && <p className="p-toolbar__desc" data-rk-e742d8="">{description}</p>}
          <p className="p-toolbar__count" aria-live="polite" data-rk-e742d8="">
            <span data-result-count={target} data-rk-e742d8="">{total}</span> {countLabel}
          </p>
        </div>
        <div className="p-toolbar__sort" data-rk-e742d8="">
          <ModernSelect label="Sort by" id={`${filtersId}-sort`} name="sort" options={sortOptions} />
        </div>
      </div>
      </div>
    </>
  );
}

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

.p-toolbar-cq:where([data-rk-e742d8]) { container-type: inline-size; }
.p-toolbar:where([data-rk-e742d8]) {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-6, 1.5rem);
  padding-bottom: var(--sp-5, 1.25rem);
  margin-bottom: var(--sp-6, 1.5rem);
  border-bottom: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.05));
}
.p-toolbar--stacked:where([data-rk-e742d8]) { flex-direction: column; align-items: stretch; }
.p-toolbar__title:where([data-rk-e742d8]) { margin: 0; font-size: clamp(1.8rem, 4vw, 2.6rem); font-weight: 900; letter-spacing: -0.035em; color: var(--c-text-1, #fff); }
.p-toolbar__desc:where([data-rk-e742d8]) { max-width: 40rem; margin: var(--sp-2, 0.5rem) 0 0; color: var(--c-text-2, #a3a3a3); }
.p-toolbar__count:where([data-rk-e742d8]) { margin: var(--sp-2, 0.5rem) 0 0; font-size: 0.85rem; color: var(--c-text-2, #a3a3a3); }
.p-toolbar__count:where([data-rk-e742d8]) span:where([data-rk-e742d8]) { color: var(--c-text-1, #fff); font-weight: 700; }
.p-toolbar__sort:where([data-rk-e742d8]) { width: 220px; flex-shrink: 0; }

@container (max-width: 640px) {
  .p-toolbar:where([data-rk-e742d8]) { flex-direction: column; align-items: stretch; }
  .p-toolbar__sort:where([data-rk-e742d8]) { width: 100%; }
}

Краткая информация

Категория
E-commerce
Имя файла
ProductToolbar.tsx
Зависимости
Нет — чистый React + CSS
Теги
ecommerceshoptoolbarsortcatalog