Display

Media Preview Row

Строка с медиа-превью, у которого место занято пропорцией 16 / 9 до загрузки картинки: строка не прыгает, а длительность продублирована текстом.

  • item
  • row
  • media
  • thumbnail

Превью

1440pxHost theme

Использовать с AI

  1. 1. Скопируйте ссылку.
  2. 2. Напишите агенту своими словами и вставьте её в предложение.
  3. 3. Агент откроет ссылку и поставит компонент из registry.

размести это в шапке: https://vibeui.ru/c/item-005

  • Как собрать каталог компонентов за вечерРоман Тищенко · 3 дня назад · 12:40
  • Показать полную инструкцию

    То, что лежит по ссылке в развёрнутом виде. Нужна, если агент не может открыть ссылку — тогда вставьте этот текст целиком.

    # Установка и размещение «item-005» (Media Preview Row) из VibeUI
    
    ## 1. Сначала установка — не пропускай, не пересоздавай
    Выполни эту команду до того, как писать код:
    npx shadcn@latest add https://vibeui.ru/r/item-005.json
    
    Registry item: https://vibeui.ru/r/item-005.json
    Ставится в: components/vibeui/item-005.tsx (точный путь следует алиасам components.json этого проекта).
    npm-зависимости: нет.
    Registry-зависимости: нет.
    
    Ставь из реестра. Не пересоздавай по описанию, не подменяй похожим
    компонентом из другой библиотеки и не переписывай под стиль проекта.
    
    ## 2. Что это
    Строка с медиа-превью, у которого место занято пропорцией 16 / 9 до загрузки картинки: строка не прыгает, а длительность продублирована текстом.
    
    Строка списка с превью 16 / 9, меткой длительности и названием в две строки. Ноль зависимостей, один файл.
    
    ## 3. Как использовать
    import { Item005 } from "@/components/vibeui/item-005"
    
    <Item005 title="Разбор кейса" badge="08:12" image="/cover.jpg" />
    
    Полный список пропсов — в установленном файле.
    
    ## 4. Куда поставить
    Это мелкий inline-компонент. Поставь его ровно туда, куда просил
    пользователь, внутрь существующей разметки. Не создавай ради него
    новую страницу, секцию или обёртку. Если на этом месте уже стоит похожий
    контрол — замени его, а не добавляй второй.
    
    Куда поставить: ___
    (Эту строку заполняет пользователь. Если она осталась пустой — спроси,
    а не угадывай.)
    
    ## 5. Сохранить как установлено
    - локальную палитру --vibeui-item-005-* и не подменять её токенами вашей темы
    - aspect-ratio у превью: без него строка прыгает в момент загрузки картинки
    - градиентную заглушку: пустое место той же высоты честнее, чем схлопнувшаяся строка
    - дубль длительности текстом: подпись поверх картинки скринридеру не видна
    - alt="" у картинки — она декоративна, смысл несёт название рядом
    - обрезку названия двумя строками: третья ломает ритм списка
    
    ## 6. Можно менять
    - title и meta — название и строку автора с датой
    - badge — длительность или другую короткую метку
    - image — адрес картинки, акцент через проп accent
    - ширину превью и ширину строки
    
    ## 7. Правила
    - Компонент рендерит обычный тег img: next/image сюда не подставляйте, блок обязан работать вне Next.
    - Без пропа image остаётся градиент — это штатное состояние, а не ошибка.
    - Оберните строки в <ul>: одиночный li без списка невалиден.
    - Не выносите CSS-переменные в globals.css: компонент обязан оставаться одним файлом.
    
    ## 8. Проверка
    - рендерится без ошибок в консоли;
    - выглядит так же, как превью на странице VibeUI, откуда взята ссылка;
    - если нет — ты изменил что-то из раздела 5, верни как было.

    Для разработчика

    npx shadcn@latest add https://vibeui.ru/r/item-005.json
    https://vibeui.ru/r/item-005.json

    Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-item-005-*. Серверный: хуков нет. Превью — блок фиксированной ширины с aspect-ratio 16 / 9 и overflow:hidden; без пропа image в нём лежит градиентная заглушка, поэтому высота строки одинакова с картинкой и без неё. Метка длительности лежит поверх превью под aria-hidden и повторена во вторичной строке текстом. Название обрезается двумя строками через -webkit-line-clamp.

    Исходник компонента

    Тот же файл, который поставит агент. Нужен, если вы предпочитаете скопировать код руками.

    import type { ComponentPropsWithoutRef, CSSProperties } from "react"
    
    export type Item005Props = Omit<
      ComponentPropsWithoutRef<"li">,
      "children" | "title"
    > & {
      title?: string
      meta?: string
      badge?: string
      image?: string
      accent?: string
    }
    
    // Идея компонента: строка с медиа-превью. Место под картинку занято до её
    // загрузки: превью задано пропорцией 16 / 9 и фиксированной шириной, поэтому
    // строка не прыгает, когда файл наконец приехал. Без картинки в том же
    // прямоугольнике лежит градиентная заглушка, а не пустота — высота строки
    // одинакова в обоих случаях. Длительность лежит поверх превью и продублирована
    // текстом для скринридера: подпись на картинке ему не видна.
    const STYLES = `
    :where([data-vibeui-block="item-005"]){
    --vibeui-item-005-bg:oklch(1 0 0);
    --vibeui-item-005-fg:oklch(0.23 0.014 265);
    --vibeui-item-005-muted:oklch(0.56 0.014 265);
    --vibeui-item-005-border:oklch(0.9 0.006 265);
    --vibeui-item-005-accent:oklch(0.55 0.19 262);
    --vibeui-item-005-shade:oklch(0.2 0.02 265 / 72%);
    --vibeui-item-005-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
    }
    [data-vibeui-block="item-005"]{
    display:flex;align-items:center;gap:0.75rem;
    width:100%;max-width:24rem;box-sizing:border-box;padding:0.625rem 0.75rem;
    list-style:none;
    background:var(--vibeui-item-005-bg);
    border:1px solid var(--vibeui-item-005-border);border-radius:0.75rem;
    font-family:var(--vibeui-item-005-font);color:var(--vibeui-item-005-fg);
    }
    [data-vibeui-block="item-005"] *{box-sizing:border-box}
    /* Место под превью занято пропорцией: строка не прыгает после загрузки. */
    [data-vibeui-block="item-005"] [data-part="media"]{
    position:relative;flex:none;width:5.5rem;aspect-ratio:16 / 9;
    border-radius:0.5rem;overflow:hidden;
    background:linear-gradient(135deg,
    color-mix(in oklab,var(--vibeui-item-005-accent) 30%,white),
    color-mix(in oklab,var(--vibeui-item-005-accent) 70%,black));
    }
    [data-vibeui-block="item-005"] [data-part="media"] img{
    display:block;width:100%;height:100%;object-fit:cover;
    }
    [data-vibeui-block="item-005"] [data-part="badge"]{
    position:absolute;right:0.25rem;bottom:0.25rem;
    padding:0 0.25rem;border-radius:0.25rem;
    background:var(--vibeui-item-005-shade);color:oklch(1 0 0);
    font-size:0.625rem;font-weight:650;line-height:1.4;font-variant-numeric:tabular-nums;
    }
    [data-vibeui-block="item-005"] [data-part="text"]{flex:1 1 auto;min-width:0;display:grid;gap:0.1875rem}
    [data-vibeui-block="item-005"] [data-part="title"]{
    font-size:0.875rem;font-weight:650;line-height:1.3;
    display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;
    }
    [data-vibeui-block="item-005"] [data-part="meta"]{font-size:0.75rem;line-height:1.35;color:var(--vibeui-item-005-muted)}
    @media (prefers-reduced-motion:reduce){[data-vibeui-block="item-005"] *{animation:none!important;transition:none!important}}
    `
    
    /**
     * Строка списка с медиа-превью фиксированной пропорции и меткой длительности.
     * Один файл, ноль зависимостей, собственная палитра.
     */
    export function Item005({
      title = "Как собрать каталог компонентов за вечер",
      meta = "Роман Тищенко · 3 дня назад",
      badge = "12:40",
      image,
      accent,
      className,
      style,
      ...props
    }: Item005Props) {
      const palette = {
        ...(accent ? { "--vibeui-item-005-accent": accent } : null),
        ...style,
      } as CSSProperties
    
      return (
        <>
          <style href="vibeui-item-005" precedence="medium">
            {STYLES}
          </style>
          <li
            {...props}
            data-vibeui-block="item-005"
            className={className}
            style={palette}
          >
            <span data-part="media">
              {image ? <img src={image} alt="" /> : null}
              {badge ? (
                <span data-part="badge" aria-hidden="true">
                  {badge}
                </span>
              ) : null}
            </span>
            <span data-part="text">
              <span data-part="title">{title}</span>
              <span data-part="meta">
                {meta}
                {badge ? ` · ${badge}` : null}
              </span>
            </span>
          </li>
        </>
      )
    }