Обзор
Компоненты
- Accordion
- Alert
- Alert Dialog
- Autocomplete
- Auth Surface
- Avatar
- Badge
- Browse Catalog Dialog
- Button
- Card
- Checkbox
- Checkbox Group
- Collapsible
- Combobox
- Command
- Connector Setup Dialog
- Cookie Banner
- Dialog
- Directory Card
- Directory Detail
- Directory Skeleton
- DrawerНовое
- Token Parts Input
- Empty
- Field
- Fieldset
- File Preview Modal
- File Preview Skeleton
- Form
- Frame
- Group
- Icon
- Input
- Input Group
- Kbd
- Label
- Legal Shell
- Menu
- Mermaid Diagram
- Mind Map Diagram
- Not Found Screen
- Onboarding Frame
- Popover
- PDF Thumbnail
- Personalization Landing
- Preview Card
- Pricing Page
- Progress
- Radio Group
- Ring Spinner
- Scroll Area
- Select
- Separator
- Settings Page
- Settings Skills
- Settings Connectors
- Settings Capabilities
- Settings Usage
- Settings Account
- Settings Billing
- Sheet
- Sidebar
- Skeleton
- Skill Create Dialog
- Slider
- Spinner
- Stat
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Tooltip
AI-компоненты
- Компоненты AI
- Chat Conversation
- Chat Message
- Chat Response
- Chat Suggestion
- Chat Prompt Input
- Slash Highlighted Textarea
- Chat Search Dialog
- Chat Skill Doc
- Chat Connector Detail
- Chat Attachments
- Chat File Card
- Chat Token Chip
- Chat Code Block
- Chat Image
- Chat Inline Citation
- Chat Sources
- Chat Web Search
- Chat Research
- Chat Source
- Chat Actions
- Chat Context
- Chat Loader
- Chat Compaction
- Chat Timeline
- Chat Snippet
- Chat Terminal
- Chat Stack Trace
- Chat Test Results
- Chat File Tree
- Chat Environment Variables
- Chat Audio Player
- Chat Transcription
- Chat Speech Input
- Chat Mic Selector
- Chat Voice Selector
- Chat Agent
- Chat Persona
- Chat Connection
- Chat Connector Suggestion
- Chat Queue
- Chat Checkpoint
- Chat Confirmation
- Chat Artifact
- Chat JSX Preview
- Chat Schema Display
- Chat Package Info
- Chat Commit
- Chat Plan
- Chat Open In Chat
- Chat Sandbox
- Chat Model Selector
- Chat Canvas
- Chat Node
- Chat Edge
Ресурсы
Chat Source
Примитивы отображения источников — favicon, стек favicon с +N, сводка по доменам и сгруппированные результаты.
неизвестный источник
"use client";
import { ChatSourceFavicon } from "@/components/ui/chat-source";
const sources = [
{ domain: "react.dev" },
{ domain: "nextjs.org" },
{ domain: "base-ui.com" },
];
export default function Particle() {
return (
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
{sources.map((source) => (
<div className="flex items-center gap-2 text-sm" key={source.domain}>
<ChatSourceFavicon domain={source.domain} size={18} />
<span className="font-medium text-foreground">{source.domain}</span>
</div>
))}
{/* favicon-fallback: empty domain resolves to the globe icon */}
<div className="flex items-center gap-2 text-sm">
<ChatSourceFavicon size={18} />
<span className="text-muted-foreground">неизвестный источник</span>
</div>
</div>
</div>
);
}
Установка
pnpm dlx shadcn@latest add @oracul/chat-source
Использование
import {
ChatSourceDomains,
ChatSourceFavicon,
ChatSourceFaviconStack,
ChatSourceGroups,
} from "@/components/ui/chat-source";
// Одиночная favicon с фоллбэком на глобус
<ChatSourceFavicon domain="react.dev" />
// Стек favicon с +N переполнением
<ChatSourceFaviconStack
max={3}
sources={[
{ domain: "react.dev" },
{ domain: "nextjs.org" },
{ domain: "base-ui.com" },
{ domain: "tailwindcss.com" },
]}
/>
// Сводка «источники по доменам»
<ChatSourceDomains
domains={[
{ domain: "wikipedia.org", count: 12 },
{ domain: "github.com", count: 7 },
{ domain: "arxiv.org", count: 3 },
]}
total={28}
/>
// Сгруппированные результаты поиска
<ChatSourceGroups
groups={[
{
id: "q1",
label: "next.js 16 cache components",
sources: [
{
id: "r1",
url: "https://nextjs.org/docs",
title: "Next.js Docs",
domain: "nextjs.org",
},
],
},
]}
/>Семейство — четыре независимых примитива поверх плоского API (данные внутрь, колбэки наружу). Без зависимостей от типов приложения. <img> нативный (фреймворк-агностично), loading="lazy".
Состояния
ChatSourceFavicon
- loaded —
srcили резолв поdomainотдаёт картинку. - favicon-fallback —
onErrorили отсутствиеsrc→GlobeIcon. - no-favicon-src — пустой
domain(резолвер вернулnull) → сразуGlobeIcon. - размеры:
size<=16запрашиваетsz=32,size>16—sz=64. Дефолт 16. URL-провайдер переопределяется черезfaviconSrc.
ChatSourceFaviconStack
"use client";
import { ChatSourceFaviconStack } from "@/components/ui/chat-source";
const sources = [
{ domain: "react.dev" },
{ domain: "nextjs.org" },
{ domain: "base-ui.com" },
{ domain: "tailwindcss.com" },
{ domain: "wikipedia.org" },
{ domain: "github.com" },
{ domain: "arxiv.org" },
];
export default function Particle() {
return (
<div className="flex flex-col gap-6">
<div className="flex items-center gap-3 text-muted-foreground text-sm">
<ChatSourceFaviconStack max={1} sources={sources.slice(0, 1)} />
<span>один источник</span>
</div>
<div className="flex items-center gap-3 text-muted-foreground text-sm">
<ChatSourceFaviconStack max={3} sources={sources.slice(0, 3)} />
<span>три источника</span>
</div>
<div className="flex items-center gap-3 text-muted-foreground text-sm">
<ChatSourceFaviconStack max={3} sources={sources} />
<span>ещё +{sources.length - 3}</span>
</div>
</div>
);
}
- empty — нет валидных источников → рендерит
null. - single — одна favicon, без нахлёста.
- multiple — нахлёст
-ml-1.5, каждая вring-2 ring-background. - overflow — источники сверх
maxсворачиваются в чип+N.
ChatSourceDomains
ещё 6 источников
"use client";
import { ChatSourceDomains } from "@/components/ui/chat-source";
const domains = [
{ domain: "wikipedia.org", count: 14 },
{ domain: "github.com", count: 9 },
{ domain: "arxiv.org", count: 6 },
{ domain: "nature.com", count: 4 },
{ domain: "stackoverflow.com", count: 3 },
{ domain: "reddit.com", count: 2 },
{ domain: "medium.com", count: 1 },
];
export default function Particle() {
return (
<div className="w-full max-w-md">
<ChatSourceDomains domains={domains} total={42} topCount={5} />
</div>
);
}
Нажмите на карточку.
"use client";
import { useState } from "react";
import { ChatSourceDomains } from "@/components/ui/chat-source";
const domains = [
{ domain: "wikipedia.org", count: 11 },
{ domain: "github.com", count: 8 },
{ domain: "arxiv.org", count: 5 },
];
export default function Particle() {
const [selected, setSelected] = useState(false);
return (
<div className="flex w-full max-w-md flex-col gap-6">
{/* loading: counting, no rows yet -> skeleton bars */}
<ChatSourceDomains domains={[]} loading total={0} />
{/* trailing counting: rows present + still loading -> extra skeleton bar */}
<ChatSourceDomains domains={domains} loading total={24} />
{/* clickable card -> drills into all sources */}
<ChatSourceDomains
domains={domains}
onSelect={() => setSelected((s) => !s)}
total={24}
/>
<p className="text-muted-foreground text-xs">
{selected
? "Карточка выбрана — открываются все источники."
: "Нажмите на карточку."}
</p>
</div>
);
}
- loading — нет строк +
loading→ 3 skeleton-бара. - loaded — топ-N доменов: favicon + имя + count + пропорциональный бар (
count/maxCount, анимация ширины). - has-more — есть источники сверх топ-N (
remaining = total − сумма топа > 0) → строка «N more sources». Учитываетtotal, поэтому строка появляется и когда передан только топ доменов вместе с бо́льшимtotal. - trailing counting —
loadingпри наличии строк → ещё один skeleton-бар. - static / clickable — без
onSelectэтоdiv; сonSelect—buttonна всю ширину сhover:border-foreground/25и кольцомfocus-visible:ring-ringдля клавиатуры. - empty — нет доменов и
loadingвыключен → ничего не рендерит.
ChatSourceGroups
"use client";
import { ChatSourceGroups } from "@/components/ui/chat-source";
const groups = [
{
id: "q1",
label: "кеширующие компоненты в next.js 16",
sources: [
{
id: "r1",
url: "https://nextjs.org/docs/app/api-reference/directives/use-cache",
title: "Директива use cache — документация Next.js",
domain: "nextjs.org",
},
{
id: "r2",
url: "https://react.dev/reference/rsc/server-components",
title: "Серверные компоненты React",
domain: "react.dev",
},
],
},
{
id: "q2",
label: "доступность компонента collapsible в base ui",
sources: [
{
id: "r3",
url: "https://base-ui.com/react/components/collapsible",
title: "Collapsible — Base UI",
domain: "base-ui.com",
},
{
id: "r4",
url: "https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/",
title: "Паттерн Disclosure | APG | WAI",
domain: "w3.org",
},
{
id: "r5",
url: "https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details",
title: "<details>: элемент Details для раскрывающегося блока",
domain: "developer.mozilla.org",
},
],
},
];
export default function Particle() {
return (
<div className="flex w-full max-w-xl flex-col gap-8">
<ChatSourceGroups groups={groups} />
{/* empty-state fallback */}
<ChatSourceGroups groups={[]} />
</div>
);
}
- empty — нет групп →
emptyLabel(«No sources.»). - collapsed — группа закрыта: результаты скрыты, шеврон повёрнут.
- expanded — открытая группа с
count>0→ список ссылок (favicon + title + domain). - count + chevron — счётчик результатов, шеврон поворачивается на 180° при открытии.
- focus-visible — триггер группы и ссылки результатов получают кольцо
ring-ringпри навигации с клавиатуры (паттерн disclosure из APG:aria-expanded+aria-controlsсвязывает триггер с раскрываемым списком).
API
| Компонент | Описание |
|---|---|
ChatSourceFavicon | Одна favicon с фоллбэком. { domain?, src?, size?, faviconSrc?, className?, alt? } |
ChatSourceFaviconStack | Стек favicon с +N. { sources, size?, max?, faviconSrc?, className? } |
ChatSourceDomains | Сводка по доменам. { domains, total?, loading?, topCount?, onSelect?, labelFor?, moreLabel?, faviconSrc?, className? } |
ChatSourceGroups / ChatSourceGroup | Сгруппированные результаты. { groups, defaultOpen?, emptyLabel?, countLabel?, faviconSrc?, className? } |
faviconSrc?(domain, size) по умолчанию использует Google s2; верни null чтобы форсировать фоллбэк-глобус. defaultFaviconSrc экспортируется отдельно.
На этой странице