Card

File Row Card

A file card for a list: a sheet glyph carrying the extension, the name on one line, and a row of facts separated by dots — type, size, date.

  • card
  • file
  • list
  • download

Preview

1440pxHost theme

Use it with AI

  1. 1. Copy the link.
  2. 2. Write to your agent in your own words and drop the link into the sentence.
  3. 3. The agent opens the link and installs the component from the registry.

put this in the header: https://vibeui.ru/c/card-016?lang=en

Show the full instructions

What the link says, spelled out. Use it when your agent cannot open links — paste this text instead.

# Install and place "card-016" (File Row Card) from VibeUI

## 1. Install first — do not skip, do not recreate
Run this exact command before writing any code:
npx shadcn@latest add https://vibeui.ru/r/card-016.json

Registry item: https://vibeui.ru/r/card-016.json
Installs to: components/vibeui/card-016.tsx (the exact path follows this project's components.json aliases).
npm dependencies: none.
Registry dependencies: none.

Install it from the registry. Do not recreate it from the description,
do not substitute a similar component from another library, and do not
rewrite it to match the project's existing style.

## 2. What it is
A file card for a list: a sheet glyph carrying the extension, the name on one line, and a row of facts separated by dots — type, size, date.

A file card: a glyph with the extension and the type colour, the name as a one-line link, a row of facts and a download button. Zero dependencies, one file, no client JS.

## 3. How to use it
import { Card016 } from "@/components/vibeui/card-016"

<Card016
  name="Component catalog.pdf"
  kind="pdf"
  size="2.4 MB"
  updated="yesterday, 18:40"
/>

Read the installed file for the full prop list.

## 4. Where to place it
This is a small inline component. Put it exactly where the user asked,
inside the existing markup. Do not create a new page, section or
wrapper for it. If a similar control already sits in that spot,
replace it instead of adding a second one.

Placement: ___
(The user fills this line in. If it is still blank, ask where to put it
instead of guessing.)

## 5. Keep exactly as installed
- the local --vibeui-card-016-* palette — do not swap it for your theme tokens
- the extension on the glyph: the name is ellipsised, and without the glyph the file type is lost entirely
- the type colour through one variable and data-kind — a new type is then one CSS line
- type, size and date on one row: in a list of forty files the row height beats the detail
- the aria-label on the download button — a bare arrow is silent
- the title attribute on the name: the full name must stay reachable when the line is cut

## 6. You may change
- the file name through name — the glyph's extension comes from it too
- the type through kind, the size through size and the date through updated
- the button text through actionLabel and the destination through href
- the accent through the accent prop — it overrides the type colour

## 7. Rules
- The size arrives as a ready string: turning bytes into megabytes is the caller's job.
- The extension is read out of name as text — a file with no dot gets the caption "file".
- The button downloads nothing: the handler or the file link belongs to the caller.
- Do not lift the CSS variables into globals.css: the component has to stay a single file.

## 8. Verify
- it renders with no console errors;
- it looks like the preview on the VibeUI page you copied this from;
- if it does not, you changed something listed in section 5 — put it back.

For developers

npx shadcn@latest add https://vibeui.ru/r/card-016.json
https://vibeui.ru/r/card-016.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-card-016-*. Серверный: хуков нет. Значок — не иконка формата, а «уголок листа»: одна фигура на clip-path плюс псевдоэлемент загнутого угла, цвет приходит из data-kind через одну переменную. Расширение вырезается из имени и печатается на значке, поэтому обрезанное многоточием имя не теряет тип. Факты выложены рядом с разделителями на ::before, а не тремя строками: в списке из сорока файлов высота строки дороже подробностей.

Component source

The same file your agent installs. Here in case you would rather copy it by hand.

import type { ComponentPropsWithoutRef, CSSProperties } from "react"

export type Card016Props = Omit<
  ComponentPropsWithoutRef<"article">,
  "children"
> & {
  /** Имя файла вместе с расширением: из него берётся тип и подпись значка. */
  name?: string
  /** Размер готовой строкой: «2,4 МБ». Компонент ничего не пересчитывает. */
  size?: string
  /** Тип файла. Задаёт цвет значка и слово в подписи. */
  kind?: "pdf" | "doc" | "sheet" | "image" | "archive" | "code"
  /** Когда обновлён: «вчера, 18:40». */
  updated?: string
  actionLabel?: string
  href?: string
  accent?: string
}

// Идея компонента: строка файлового списка. Значок — не иконка формата,
// а «уголок листа», нарисованный градиентом с вырезом: одна фигура на все
// типы, цвет и три буквы делают остальное. Длинное имя обрезается с конца
// первой строки, но расширение вынесено в значок и не теряется.
const STYLES = `
:where([data-vibeui-block="card-016"]){
--vibeui-card-016-bg:oklch(1 0 0);
--vibeui-card-016-fg:oklch(0.22 0.015 265);
--vibeui-card-016-muted:oklch(0.56 0.013 265);
--vibeui-card-016-border:oklch(0.91 0.006 265);
--vibeui-card-016-kind:oklch(0.58 0.16 265);
--vibeui-card-016-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="card-016"]{
display:flex;align-items:center;gap:0.75rem;
width:100%;max-width:24rem;box-sizing:border-box;padding:0.75rem 0.875rem;
background:var(--vibeui-card-016-bg);color:var(--vibeui-card-016-fg);
border:1px solid var(--vibeui-card-016-border);border-radius:0.8125rem;
font-family:var(--vibeui-card-016-font);
transition:border-color .16s ease;
}
[data-vibeui-block="card-016"]:hover{
border-color:color-mix(in oklab,var(--vibeui-card-016-kind) 35%,var(--vibeui-card-016-border));
}
[data-vibeui-block="card-016"][data-kind="pdf"]{--vibeui-card-016-kind:oklch(0.58 0.19 27)}
[data-vibeui-block="card-016"][data-kind="doc"]{--vibeui-card-016-kind:oklch(0.55 0.16 255)}
[data-vibeui-block="card-016"][data-kind="sheet"]{--vibeui-card-016-kind:oklch(0.55 0.15 152)}
[data-vibeui-block="card-016"][data-kind="image"]{--vibeui-card-016-kind:oklch(0.6 0.16 320)}
[data-vibeui-block="card-016"][data-kind="archive"]{--vibeui-card-016-kind:oklch(0.66 0.15 78)}
[data-vibeui-block="card-016"][data-kind="code"]{--vibeui-card-016-kind:oklch(0.56 0.13 200)}
/* Уголок листа вырезан clip-path: одна фигура на все типы файлов. */
[data-vibeui-block="card-016"] [data-part="glyph"]{
position:relative;display:flex;align-items:flex-end;justify-content:center;flex:none;
width:2.375rem;height:2.875rem;padding-bottom:0.375rem;box-sizing:border-box;
clip-path:polygon(0 0,68% 0,100% 26%,100% 100%,0 100%);
background:linear-gradient(160deg,
color-mix(in oklab,var(--vibeui-card-016-kind) 16%,oklch(1 0 0)),
color-mix(in oklab,var(--vibeui-card-016-kind) 30%,oklch(1 0 0)));
color:color-mix(in oklab,var(--vibeui-card-016-kind) 82%,oklch(0.2 0.02 265));
font-size:0.5625rem;font-weight:750;letter-spacing:0.04em;text-transform:uppercase;
}
[data-vibeui-block="card-016"] [data-part="glyph"]::before{
content:"";position:absolute;inset-block-start:0;inset-inline-end:0;
width:32%;height:26%;
background:color-mix(in oklab,var(--vibeui-card-016-kind) 45%,oklch(1 0 0));
}
[data-vibeui-block="card-016"] [data-part="body"]{
flex:1;min-width:0;display:flex;flex-direction:column;gap:0.1875rem;
}
[data-vibeui-block="card-016"] [data-part="name"]{
margin:0;font-size:0.875rem;font-weight:620;line-height:1.3;
overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
[data-vibeui-block="card-016"] [data-part="name"] a{color:inherit;text-decoration:none;outline:none}
[data-vibeui-block="card-016"] [data-part="name"] a:hover{text-decoration:underline}
[data-vibeui-block="card-016"] [data-part="name"] a:focus-visible{
outline:2px solid var(--vibeui-card-016-kind);outline-offset:2px;border-radius:0.25rem;
}
/* Тип, размер и дата — один ряд с точками-разделителями, а не три строки:
   в списке из сорока файлов высота строки дороже подробностей. */
[data-vibeui-block="card-016"] [data-part="facts"]{
display:flex;align-items:center;flex-wrap:wrap;gap:0.375rem;
margin:0;font-size:0.75rem;color:var(--vibeui-card-016-muted);
}
[data-vibeui-block="card-016"] [data-part="facts"] > * + *::before{
content:"·";margin-inline-end:0.375rem;color:var(--vibeui-card-016-border);
}
[data-vibeui-block="card-016"] [data-part="size"]{font-variant-numeric:tabular-nums}
[data-vibeui-block="card-016"] [data-part="action"]{
appearance:none;cursor:pointer;flex:none;
display:flex;align-items:center;justify-content:center;
width:2rem;height:2rem;border-radius:0.5rem;
border:1px solid var(--vibeui-card-016-border);background:oklch(1 0 0);
color:var(--vibeui-card-016-muted);
transition:background-color .16s ease,color .16s ease;
}
[data-vibeui-block="card-016"] [data-part="action"]:hover{
background:color-mix(in oklab,var(--vibeui-card-016-kind) 10%,oklch(1 0 0));
color:color-mix(in oklab,var(--vibeui-card-016-kind) 80%,oklch(0.2 0.02 265));
}
[data-vibeui-block="card-016"] [data-part="action"]:focus-visible{
outline:2px solid var(--vibeui-card-016-kind);outline-offset:2px;
}
[data-vibeui-block="card-016"] [data-part="action"] svg{width:1rem;height:1rem}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="card-016"] *{animation:none!important;transition:none!important}}
`

const KIND_TEXT = {
  pdf: "Документ PDF",
  doc: "Текстовый документ",
  sheet: "Таблица",
  image: "Изображение",
  archive: "Архив",
  code: "Исходный код",
}

function extension(name: string) {
  const tail = name.split(".").pop()

  return tail && tail !== name ? tail.slice(0, 4) : "файл"
}

/**
 * Карточка файла: значок с расширением, имя, тип, размер и дата.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Card016({
  name = "Каталог компонентов, версия 4.pdf",
  size = "2,4 МБ",
  kind = "pdf",
  updated = "вчера, 18:40",
  actionLabel = "Скачать файл",
  href = "#",
  accent,
  className,
  style,
  ...props
}: Card016Props) {
  const palette = {
    ...(accent ? { "--vibeui-card-016-kind": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-card-016" precedence="medium">
        {STYLES}
      </style>
      <article
        {...props}
        data-vibeui-block="card-016"
        data-kind={kind}
        className={className}
        style={palette}
      >
        <span data-part="glyph" aria-hidden="true">
          {extension(name)}
        </span>
        <div data-part="body">
          <p data-part="name" title={name}>
            {href ? <a href={href}>{name}</a> : name}
          </p>
          <p data-part="facts">
            <span>{KIND_TEXT[kind]}</span>
            {size ? <span data-part="size">{size}</span> : null}
            {updated ? <span>{updated}</span> : null}
          </p>
        </div>
        <button data-part="action" type="button" aria-label={actionLabel}>
          <svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
            <path
              d="M8 2.5v8m0 0L4.8 7.3M8 10.5l3.2-3.2M3 13h10"
              stroke="currentColor"
              strokeWidth="1.6"
              strokeLinecap="round"
              strokeLinejoin="round"
            />
          </svg>
        </button>
      </article>
    </>
  )
}