@brett_lamy/ui

中文文档
5.17.0发布于 2026/11/08MITTypeScriptES Module近 30 天下载 2655

iOS-flavored React components on react-aria-components, Tailwind v4 and cva — containers, lists, tokens, Composer, PencilKit. Also a shadcn registry (source-copy items and app blocks).

原始使用文档
文档目录8 节
  1. Install
  2. IndexBar
  3. Exports
  4. Team chat
  5. Workbench
  6. Image annotation
  7. PencilKit
  8. Workspace development

@brett_lamy/ui

Touch-first React components inspired by UIKit container patterns, built on react-aria-components and Tailwind v4. Colors, radius and font come from your shadcn theme (CSS variables); the iOS look ships as an optional theme. The library has lists, navigation, adaptive split views, drawers, sheets, a reusable jump rail, the Composer (a Markdown prompt box), PencilKit drawing and the message scroller. Product-specific compositions (an IDE workbench, a team-chat shell, a map chat, …) are blocks, installed from the shadcn registry rather than this package.

Install

Two ways, from the same source files. The docs explain both: Installation and Optimization.

  • shadcn registry (recommended for products). npx shadcn add https://blamy.github.io/ui/r/split-view.json copies the source of one component and what it imports into your app (components/ui/…, lib/…), so you own the files and only ship what you use.
  • npm. One dependency with everything in it:
npm i @brett_lamy/ui

Import the stylesheet once — and, for the iOS palette, the bl-theme (it sets shadcn's CSS variables; leave it out to use your own shadcn theme). BLProvider picks light/dark and a tint for a subtree, and ThemeScope scopes any set of variables to a subtree:

@import '@brett_lamy/ui/styles.css';
@import '@brett_lamy/ui/theme.css'; /* optional: the iOS palette */
import { BLProvider, NavigationStack } from '@brett_lamy/ui';

export function App() {
  return (
    <BLProvider tint="#0a84ff">
      <NavigationStack screens={screens} onPop={handlePop} />
    </BLProvider>
  );
}

The package is ESM with one output module per source module (sideEffects is limited to CSS), so bundlers drop what you don't import; component modules start with 'use client' for Next.js.

React 19 is required (the components use use(Context), <Context value> and ref as a prop).

IndexBar

IndexBar accepts application-defined string or numeric keys. Labels are optional, so dense timelines can render as dots, and preview accepts any React node. Hover previews without navigating; pointer drag and keyboard navigation (ArrowUp, ArrowDown, Home, End) commit a stop.

import { IndexBar, type IndexBarItem } from '@brett_lamy/ui';

const stops: IndexBarItem<number>[] = turns.map((turn, index) => ({
  key: turn.sequence,
  label: index % 5 === 0 ? String(index + 1) : undefined,
  caption: turn.author,
  preview: <span>{turn.summary}</span>,
}));

<IndexBar
  items={stops}
  label="Jump to conversation turn"
  onJump={(sequence) => scrollToTurn(sequence)}
/>;

If items is omitted or empty, the component retains its A-Z form:

<IndexBar
  avail={new Set(['A', 'B', 'K'])}
  onLetter={(letter) => scrollToSection(letter)}
/>;

Exports

  • Containers: NavigationStack, SplitView, Credenza, SideDrawer, AdaptivePane, Sidebar
  • Lists and navigation: List, List.Section, List.Row, IndexBar, TabBar, TabView, EditBar
  • Inputs and feedback: SearchField, Switch, Segmented, plus shadcn-style primitives (Button, Dialog, Sheet, Select, ComboBox, Tabs, …)
  • Motion: springs, springCss, TextMorph, NumberMorph, AnimatedHeight, ContentSwap, Celebrate
  • Foundations: BLProvider, ThemeScope, AppearanceProvider, Icon, Avatar, Spinner
  • Team chat: ChatShell and its regions (ChatShellNav, ChatShellSidebar, ChatShellMain, ChatShellHeader, ChatShellAside, ChatShellPanel…), FloatingSheet, FloatingChat, ChatColumn, ArtifactChatContainer (the Discord parts — channels, messages, threads, members — live in the discord-clone registry block)
  • Workbench: WorkbenchShell and its parts (WorkbenchSidebar, WorkbenchMain, WorkbenchHeader, WorkbenchDock, WorkbenchPanel, WorkbenchTabBar, …), conversation parts (Conversation, UserMessage, AssistantMessage, WorkLog, ToolCall, …), TerminalHeader / TerminalBody, surfaces (SurfacePicker, SurfaceBrowser, SurfaceFiles, SurfaceDiff, SurfaceAgents), Composer and its parts, ModelPicker, MessageScroller, MarkdownView
  • Demo apps: MapChatDemo, DeliveryTrackingDemo, SidebarDemo (full apps — Discord, T3 Code, GitHub — are registry blocks)

Every component exports its props type from the package root. See the Storybook catalog for interaction and responsive examples.

Team chat

ChatShell is a thin layout root: it measures its own width, owns the compact navigation drawer and applies the chat palette. Everything inside is a part you place yourself — leave out what a layout doesn't need. Parts read shell state (width, compact, navOpen, setNavOpen) through useChatShell(). A full Discord-style app ships as the discord-clone registry block, with its channel, message, member and composer parts (install it to own and edit them).

import {
  ChatShell, ChatShellNav, ChatShellSidebar, ChatShellMain, ChatShellHeader, ChatShellNavTrigger, ChatShellTitle,
  ChatShellFooter, TabView, TabViewBar, TabViewList, TabViewTab, TabViewIndicator, SidebarContent, SidebarSection,
  SidebarItem, Composer, ComposerCard, ComposerInput, ComposerFooter, ComposerSpacer, ComposerSend,
} from '@brett_lamy/ui';

export function Chat() {
  const [channel, setChannel] = useState('general');
  return (
    <ChatShell breakpoint={880}>
      <ChatShellNav>
        <TabView orientation="vertical" defaultSelectedKey="hq" className="contents">
          <TabViewBar variant="workspace">
            <TabViewList aria-label="Workspaces">
              <TabViewTab id="hq" textValue="HQ"><TabViewIndicator variant="pill" />{/* tile */}</TabViewTab>
            </TabViewList>
          </TabViewBar>
        </TabView>
        <ChatShellSidebar>
          <SidebarContent>
            <SidebarSection title="Team">
              <SidebarItem label="general" active={channel === 'general'} onPress={() => setChannel('general')} />
              <SidebarItem label="dev" badge={2} active={channel === 'dev'} onPress={() => setChannel('dev')} />
            </SidebarSection>
          </SidebarContent>
        </ChatShellSidebar>
      </ChatShellNav>
      <ChatShellMain>
        <ChatShellHeader><ChatShellNavTrigger /><ChatShellTitle>{channel}</ChatShellTitle></ChatShellHeader>
        <div role="log" className="min-h-0 flex-1 overflow-y-auto">{/* messages */}</div>
        <ChatShellFooter>
          <Composer onSubmit={send}>
            <ComposerCard>
              <ComposerInput placeholder={'Message #' + channel} />
              <ComposerFooter><ComposerSpacer /><ComposerSend /></ComposerFooter>
            </ComposerCard>
          </Composer>
        </ChatShellFooter>
      </ChatShellMain>
    </ChatShell>
  );
}

ArtifactChatContainer keeps a conversation beside an artifact when space permits. Below its container breakpoint the artifact keeps the full canvas and the Composer floats over it, with the transcript on a draggable top bump.

<ArtifactChatContainer working={isWorking}>
  <ArtifactChatContainer.Chat><Conversation /></ArtifactChatContainer.Chat>
  <ArtifactChatContainer.Composer><MyComposer /></ArtifactChatContainer.Composer>
  <ArtifactChatContainer.Content><Artifact /></ArtifactChatContainer.Content>
</ArtifactChatContainer>

Workbench

An adaptive IDE scaffold built from parts: a thin WorkbenchShell root that measures itself and owns region state, and parts you place yourself. Each part picks its presentation from the shell's width class — desktop columns (1120px and wider), a right-edge panel drawer (760–1119px), or a hamburger sidebar, compact panel page, bottom tab bar, and dismissible-sheet dock below that — measured from the shell's own container, not the viewport. The full T3 Code-style app is the t3-clone registry block.

import {
  WorkbenchShell, WorkbenchSidebar, WorkbenchMain, WorkbenchHeader, WorkbenchSidebarTrigger, WorkbenchTitle,
  WorkbenchActions, WorkbenchDockTrigger, WorkbenchPanelTrigger, WorkbenchDock, WorkbenchPanel,
  WorkbenchTabBar, WorkbenchTab,
} from '@brett_lamy/ui';

export function Workbench() {
  return (
    <WorkbenchShell tint="#0a84ff">
      <WorkbenchSidebar><Threads /></WorkbenchSidebar>
      <WorkbenchMain>
        <WorkbenchHeader>
          <WorkbenchSidebarTrigger />
          <WorkbenchTitle project="cookbook">{thread.title}</WorkbenchTitle>
          <WorkbenchActions><WorkbenchDockTrigger /><WorkbenchPanelTrigger /></WorkbenchActions>
        </WorkbenchHeader>
        <Chat />
        <WorkbenchDock><Terminal /></WorkbenchDock>
      </WorkbenchMain>
      <WorkbenchPanel><Surfaces /></WorkbenchPanel>
      <WorkbenchTabBar value={surface} onValueChange={setSurface}>
        <WorkbenchTab id="chat" icon="msg">Chat</WorkbenchTab>
        <WorkbenchTab id="diff" icon="diff">Diff</WorkbenchTab>
      </WorkbenchTabBar>
    </WorkbenchShell>
  );
}

Descendants read and drive the shell with useWorkbenchShell() (sidebarOpen, dockOpen, panelOpen, panelFullscreen, their setters and toggles, widthClass, compact).

Composer is compositional (like shadcn's InputGroup): Composer owns the draft, ComposerCard holds ComposerInput (the @brett_lamy/docstream-editor WYSIWYG editor; pasted images become attachment chips) and addons (ComposerFooter, ComposerSelect, ModelPicker, ComposerSend, …), and ComposerBumps attach above or below the card. WorkbenchComposer is the default composition. MarkdownView renders replies with @brett_lamy/docstream; SurfaceFiles and SurfaceDiff use @pierre/trees and @pierre/diffs.

Image annotation

Pressing an attachment opens AnnotateLightbox, which draws on the image with PencilKitAnnotator by default; Save flattens the strokes into the image. The annotator is pluggable — swap it for every Composer below a ComposerAnnotatorProvider, or per composer with the annotator prop (annotator={null} opts out to a plain preview):

import { ComposerAnnotatorProvider } from '@brett_lamy/ui';

<ComposerAnnotatorProvider annotator={MyAnnotator}>
  <App />
</ComposerAnnotatorProvider>;
// or per composer: <Composer annotator={MyAnnotator}> (annotator={null} opts out)

An annotator is a component that calls its children with { canvas, toolbar?, title? }: the canvas is laid over the image (its first <svg> is flattened into the image on save), the toolbar sits under it.

PencilKit

PencilKit's drawing surface in BL UI's language, on perfect-freehand: PencilCanvas, the tool / ink / width pickers and undo bar, usePencilHistory, and StrokePath for rendering saved strokes.

import { PencilKitDemo, demoStrokes } from '@brett_lamy/ui';

<div style={{ position: 'relative', height: 540 }}>
  <PencilKitDemo defaultStrokes={demoStrokes()} style={{ position: 'absolute', inset: 0 }} />
</div>;

Workspace development

pnpm nx build @brett_lamy/ui
pnpm nx lint @brett_lamy/ui

版本与发布记录

@brett_lamy/ui 共发布 24 个版本,首次发布于 2026/08/27;过去 12 个月发布了 24 个版本。

版本发布日期距今
5.17.0latest2026/11/08今天
5.16.02026/11/071 天前
5.15.12026/11/044 天前
5.15.02026/11/044 天前
5.13.02026/11/044 天前
5.10.12026/10/2910 天前

关于 @brett_lamy/ui 的常见问题

@brett_lamy/ui 是什么?

iOS-flavored React components on react-aria-components, Tailwind v4 and cva — containers, lists, tokens, Composer, PencilKit. Also a shadcn registry (source-copy items and app blocks).

如何安装 @brett_lamy/ui?

在项目目录运行 npm install @brett_lamy/ui,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^19)、react-dom(^19)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@brett_lamy/ui 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@brett_lamy/ui 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@brett_lamy/ui 可以商用吗?

@brett_lamy/ui 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@brett_lamy/ui 的下载量有多少?

根据 npm 官方下载统计,@brett_lamy/ui 在 2026/10/09 – 2026/11/07 这 30 天内被下载 2,655 次,日均约 89 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@brett_lamy/ui 还在维护吗?

最新版本 5.17.0 发布于 2026/11/08(今天);至今共发布 24 个版本,其中过去 12 个月发布了 24 个;首次发布于 2026/08/27。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。