@bsuite/ui

中文文档
1.3.4发布于 2026/10/21UNLICENSEDTypeScriptES Module近 30 天下载 4230

Shared UI primitives for the BSuite apps (D2C Neon Electric + Corporate brands).

原始使用文档
文档目录17 节
  1. Components
  2. Core primitives (v0.4.0+)
  3. Storybook
  4. DotPattern
  5. Logo
  6. D2CDefaultLogo
  7. Branding Components (v0.3.0+)
  8. OklchColorPicker
  9. ColorEditorSheet
  10. BrandingCard
  11. EntitySelector (v1.2.0+)
  12. Utils
  13. cn(...classes)
  14. Module Exports
  15. Releases
  16. Release 1.3.4
  17. Custom branding CSS boundary

@bsuite/ui

Shared UI primitives for the BSuite apps (D2C Neon Electric + Corporate brands).

Ships as an npm package because Vercel deploys each BSuite app from its own GitHub repo — the parent monorepo's packages/ directory does NOT exist in the Vercel build context. Consumers install from npm with a pinned semver.

Components

Core primitives (v0.4.0+)

The package now owns the shared Tier 1 UI primitives apps should converge on:

import {
  Button,
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  EmptyState,
  ErrorBoundary,
  LoadingSpinner,
  StatusBadge,
} from '@bsuite/ui'

These primitives use BSuite/shadcn semantic token classes only (bg-primary, text-muted-foreground, border-border, text-status-success, etc.) so D2C apps can skin them through @bsuite/theme and Braden can map the same roles to the corporate palette.

Storybook

Run the shared primitive gallery from this package:

pnpm -C packages/ui storybook
pnpm -C packages/ui build-storybook

DotPattern

Full-viewport dot-pattern background. SVG <pattern> tiling — ~1 DOM node (vs ~1,800 motion.circle nodes on a typical viewport).

import { DotPattern } from '@bsuite/ui'

<div className="relative">
  <DotPattern
    width={28}
    height={28}
    cr={1.5}
    className="opacity-[0.35] text-muted-foreground"
  />
  {/* page content */}
</div>

Colour control via semantic Tailwind text-* utilities (dots use fill="currentColor").

Slot-aware, theme-aware, branding-aware logo component for all BSuite apps.

import { Logo } from '@bsuite/ui'

<Logo slot="header" colorScheme="auto" />

Inline D2C Neon Electric SVG logo (no asset dependency).

import { D2CDefaultLogo } from '@bsuite/ui'

<D2CDefaultLogo className="h-8 w-auto" />

Branding Components (v0.3.0+)

OklchColorPicker

A color input component for OKLCH color format (BSuite standard).

import { OklchColorPicker } from '@bsuite/ui'

<OklchColorPicker
  value="oklch(0.546 0.215 262.9)"
  onChange={(value) => console.log(value)}
  label="Primary Color"
  description="Your brand's primary color in OKLCH format"
/>

Features:

  • Live color preview
  • Input fields for L (lightness), C (chroma), H (hue)
  • Slider controls for easy adjustment
  • Validation of OKLCH format
  • Accessible labels and error messages

ColorEditorSheet

A sheet/dialog for editing branding colors with a slot-based color editor.

import { ColorEditorSheet, OklchColorPicker } from '@bsuite/ui'

<ColorEditorSheet
  open={isOpen}
  onOpenChange={setIsOpen}
  title="Edit Primary Color"
  description="Adjust the OKLCH color values"
  value="oklch(0.546 0.215 262.9)"
  onSave={(value) => updateBranding('primary', value)}
  renderEditor={(props) => <OklchColorPicker {...props} />}
/>

Features:

  • Slide-out sheet interface
  • Slot-based color editor (inject your own picker)
  • Save/Cancel actions
  • Responsive design
  • Accessible dialog implementation

BrandingCard

A card component for displaying and editing tenant branding configuration.

import { BrandingCard, OklchColorPicker } from '@bsuite/ui'

<BrandingCard
  branding={{
    primary: "oklch(0.546 0.215 262.9)",
    accent: "oklch(0.769 0.132 191.7)",
    logo_url: "https://example.com/logo.svg",
    company_name: "Acme Corp"
  }}
  onUpdate={async (key, value) => {
    await updateTenantBranding(key, value)
  }}
  renderColorEditor={(props) => <OklchColorPicker {...props} />}
/>

Features:

  • Display current branding values (colors, logos, company name)
  • Edit buttons for each branding property
  • Integrated color editing with ColorEditorSheet
  • Image preview for logos
  • Accessible and responsive design
  • Support for light/dark mode logo variants

Branding Properties:

  • primary - Primary action color (OKLCH)
  • accent - Accent/secondary color (OKLCH)
  • logo_url - Main logo URL
  • logo_light_url - Light mode logo variant (optional)
  • logo_dark_url - Dark mode logo variant (optional)
  • favicon_url - Favicon URL (16x16 or 32x32)
  • company_name - Company name for alt text

EntitySelector (v1.2.0+)

Generic, schema-agnostic searchable combobox for picking a row out of any Supabase table. Extracted from crm7 (AD-5, 2026-08-17) — crm7 had 65 files of adoption and two other apps had each started hand-porting their own copy at the identical path. This is now the sole implementation.

Data access is dependency-injected — the package does not own a Supabase client, since every app's client is shaped differently (Vite-SPA singleton, per-request createClient() factory, distinct storageKey, …):

import { EntitySelector } from '@bsuite/ui'
import { supabase } from '@/lib/supabase' // your app's own client

<EntitySelector<Employer>
  supabaseClient={supabase}
  table="employers"
  value={form.employer_id}
  onSelect={(emp) => setValue('employer_id', emp?.id ?? null)}
  displayField={(r) => r.business_name}
  secondaryField={(r) => r.abn ?? r.industry}
  searchColumns={['business_name', 'trading_name', 'abn']}
/>

Recommended per-app pattern: wrap the import once in a thin local file that binds your app's client, so the rest of the app's ~dozens of call sites never repeat supabaseClient={supabase}:

// src/components/entity/EntitySelector.tsx
import { EntitySelector as BaseEntitySelector, type EntitySelectorProps as BaseProps } from '@bsuite/ui'
import { supabase } from '@/lib/supabase'

export type EntitySelectorProps<T extends Record<string, unknown>> = Omit<BaseProps<T>, 'supabaseClient'>

export function EntitySelector<T extends Record<string, unknown>>(props: EntitySelectorProps<T>) {
  return <BaseEntitySelector<T> supabaseClient={supabase} {...props} />
}

Features: debounced typeahead (ilike across configured columns), optional cross-schema reads (schema prop), a filterFn escape hatch for arbitrary PostgREST filter chains, "one-shot" suggested options (rows already linked on an earlier screen, shown before any typing), an async-safe onSelect (no optimistic UI when the caller's select routes through an import/RPC step), and a distinct error state (not the same "no results" copy a genuine empty search shows).

Also exported for building custom pickers on the same visual language: Command, CommandInput, CommandList, CommandGroup, CommandItem, CommandEmpty, Popover, PopoverTrigger, PopoverContent.

Utils

cn(...classes)

shadcn-style className merger — clsx + tailwind-merge. Available for consumers that want to compose @bsuite/ui components with local overrides without pulling shadcn again.

Module Exports

The package provides subpath exports for better tree-shaking:

// Main exports
import { Logo, BrandingCard, Button, StatusBadge } from '@bsuite/ui'

// Dot pattern only
import { DotPattern } from '@bsuite/ui/dot-pattern'

// Utils only
import { cn } from '@bsuite/ui/utils'

// Branding components only
import { BrandingCard, ColorEditorSheet, OklchColorPicker } from '@bsuite/ui/branding'

// Primitive subpaths
import { Button } from '@bsuite/ui/button'
import { DialogContent } from '@bsuite/ui/dialog'
import { EmptyState } from '@bsuite/ui/empty-state'
import { EntitySelector } from '@bsuite/ui/entity-selector'
import { Command, CommandInput } from '@bsuite/ui/command'
import { Popover, PopoverContent } from '@bsuite/ui/popover'

// SVG asset
import logoSvg from '@bsuite/ui/assets/d2c-default-logo.svg'

Releases

  1. Edit source under src/.
  2. pnpm -C packages/ui test — all cases must pass.
  3. pnpm -C packages/ui build — emits dist/.
  4. Bump version in package.json (semver).
  5. Merge the package release PR to main; .github/workflows/publish-ui.yml publishes via npm Trusted Publishers (GitHub Actions OIDC, no NPM_TOKEN).
  6. Update consumer package.json to the new version, regenerate lockfile outside the bsuite tree (see root CLAUDE.md rule 7), commit the lockfile + a consumer-side bump PR.

NEVER use workspace:* or file:../packages/ui for this package in a consumer — Vercel builds can't resolve those.

Release 1.3.4

Finalises the 1.3.4-next.2 source without a further runtime change. That preview already rejects resource-loading image functions in every stylesheet context and includes the accessible selector clear control below. Consumer adoption of the exact stable version follows publication and a check of existing stored branding CSS.

Custom branding CSS boundary

Resource-loading image functions are rejected whole-stylesheet, after escape decoding, case folding and vendor-prefix removal, in every context (rules, @media, @supports preludes, keyframes, @property and custom-property values, var() fallbacks): url, src, image, image-set, cross-fade, element and paint. Pure paint images such as linear-gradient, radial-gradient, conic-gradient and their repeating forms remain accepted, subject to the semantic-colour rule below. Stored tenant CSS that uses these functions now fails validation; measuring that stored data is a consumer-adoption step, not part of this package change.

validateCustomCss is exported from both @bsuite/ui and @bsuite/ui/branding. Call it before saving a stylesheet. An accepted result contains the unchanged stylesheet; an invalid or unsupported result contains an actionable reason and, when available, the source line. Keep the author's draft on rejection. sanitizeCustomCss retains its existing string API for every runtime sink and returns an empty string for a rejected stylesheet. Insert accepted CSS through HTMLStyleElement.textContent, never HTML interpolation.

The owning parser is CSS-tree 3.2.1. Its AST and property/descriptor grammar allow layout, typography, media/supports/container/layer rules, keyframes and bounded local custom properties. Colour consumers must use semantic theme variables; transparent and currentColor remain neutral values. Raw palette colours, endpoint colours including alpha forms, unsafe resources, protected role and shadcn definitions, and unsupported syntax reject the whole stylesheet. Escapes, variable fallbacks, alias cascades and typed custom-property initial values pass through the same boundary. Parsed @supports capability probes do not paint or redefine roles; their real rule blocks remain guarded, and resource/executable exclusions still apply throughout the stylesheet. Custom animation and grid names retain their case and are not mistaken for colour bindings. The parser and alias expansion have explicit source, token, depth and node limits.

Theme palette namespaces and their colour aliases are also protected, because theme roles consume them outside the submitted stylesheet. Tailwind colour and whole paint/effect slots (including gradients, shadows, rings and masks) cannot be redefined. Its numeric transform, position and mask geometry slots remain authorable, with colour-bearing aliases and fallbacks checked. Tests derive this boundary from both theme sources and actual emitted Tailwind utilities; this does not claim to identify arbitrary variables in unrelated external stylesheets.

Editors must surface rejection rather than silently saving empty CSS. Runtime validation is also required because existing persisted CSS predates this rule. BSU, CRM7 and Braden are the three custom-CSS consumers in the current six-app source census; published-package adoption and deployed user acceptance remain separate gates.

EntitySelector exposes an optional native sibling clear button for selected entities. Its accessible name includes the selected label. Click, Enter and Space call onSelect(null) once and return focus to the combobox without opening the picker or submitting a surrounding form. The disabled and clearable=false contracts apply to this button. Promise-returning parent saves remain authoritative: a pending clear prevents resubmission and conflicting selection/creation, rejection retains the saved selection and shows a retry message, and successful clearing follows the controlled value. Settlement returns focus only when it remains on the initiating control or was lost through disabling it; focus deliberately moved to another field is preserved. Selected-ID hydration and filtering are separate contracts and are unchanged by this repair.

版本与发布记录

@bsuite/ui 共发布 23 个版本,首次发布于 2026/04/21;过去 12 个月发布了 23 个版本。

版本发布日期距今
1.3.4latest2026/10/2119 天前
1.3.4-next.4next2026/10/2119 天前
1.3.4-next.32026/10/2119 天前
1.3.4-next.22026/10/091 个月前
1.3.4-next.12026/10/051 个月前
1.3.4-next.02026/10/051 个月前

关于 @bsuite/ui 的常见问题

@bsuite/ui 是什么?

Shared UI primitives for the BSuite apps (D2C Neon Electric + Corporate brands).

如何安装 @bsuite/ui?

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

@bsuite/ui 支持 TypeScript 吗?

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

@bsuite/ui 支持 ESM 还是 CommonJS?

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

@bsuite/ui 可以商用吗?

@bsuite/ui 使用 UNLICENSED 许可。作者声明为 UNLICENSED,即不授予公开使用许可(与公共领域的 Unlicense 不同)。使用前需获得作者授权。以上为概括,不构成法律意见,请以许可原文为准。

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

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

@bsuite/ui 还在维护吗?

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