@cinesend/ui-kit

中文文档
0.5.5发布于 2026/11/03MITTypeScriptES Module近 30 天下载 2260

Shared React components, design tokens, and CSS for CineSend apps.

原始使用文档
文档目录7 节
  1. Install
  2. Installing from git
  3. Peer dependencies
  4. Data-dense components
  5. Local kit work
  6. Releasing
  7. Stack for consuming apps

CineSend UI kit

Shared React components, tokens, and CSS for CineSend apps. This is not an app: no product screens, no API calls, no app logic.

Published to npm as @cinesend/ui-kit. The GitHub repo is BitCine/cinesend-ui-kit. There is no BitCine/ui-kit repo.

Install

npm install @cinesend/ui-kit
import { Button, TextField } from "@cinesend/ui-kit";
import "@cinesend/ui-kit/styles.css";

The stylesheet is a separate entry point and must be imported once, near your app root. It is not bundled into the JS entry.

Local development (clone this repo next to the app):

"@cinesend/ui-kit": "file:../cinesend-ui-kit"

Installing from a file: path serves the built dist/, so run npm run build here after changing the kit.

Installing from git

The older path still works while apps migrate:

"@cinesend/ui-kit": "github:BitCine/cinesend-ui-kit#staging"

dist/ is not committed, so a git install builds the kit on install via the prepare script. That means the consumer downloads this package's devDependencies and runs a full build on every npm ci. Prefer the npm release; this path exists so apps are not forced to migrate in lockstep.

Peer dependencies

Apps must provide:

  • react and react-dom (>=18.2 <20)
  • @phosphor-icons/react ^2.1.10
  • sonner ^2.0.7

Runtime dependency: @base-ui/react.

Data-dense components

Most of this kit is tile- and file-oriented, which is what Files and Screeners needed. Table, Pagination, Badge and the page states are for data-dense internal tools, and they share one rule worth stating once: they describe a page of server-held data, and never reinterpret it.

The Table component

Sorted, with row selection and a real control in each cell, light and dark:

Table with sorting and selection

The same table in dark mode

  • Table sorts nothing. A sortable header reports the sort it wants through onSortChange; whoever owns the query answers. The rows handed to it are one page of a larger set, so ordering them in the component would sort the page rather than the data, and disagree with both the total underneath and the next page. Selection is controlled for the same reason, and select-all covers only the keys on screen.

    caption is required: it is the table's accessible name and it is visually hidden. Without it a screen reader announces "table" and nothing else.

    onRowClick is a pointer shortcut, not the row's only route. Rows keep their row role, so they are not focusable and are not announced as controls — put a real link or button in one of the cells and let the row click duplicate it. Clicks that start on that control (or any other interactive element in the row) do not also run the row shortcut, so the action fires once rather than twice. The obvious alternative, role="button" plus tabIndex on the <tr>, is deliberately avoided: it overrides the implicit row role, leaves every cell without a valid row owner, breaks table navigation for a screen reader, and nests the selection checkbox inside a button.

    stickyHeader needs the height bound on the table root itself, which is what className is for:

    <Table stickyHeader className="h-[32rem]" … />
    

    Not on a wrapper around it. .cs-table carries overflow-x, and when one axis is not visible the other computes to auto — so .cs-table is the nearest scroll container. Bound a wrapper instead and the header scrolls out of view along with the table, which looks exactly like the prop not working.

    Virtualization is deliberately out of scope. Every row given is rendered. That is comfortable into the high hundreds, which covers a paged admin list. A screen that needs tens of thousands of rows at once wants a different component, not a flag on this one.

  • Pagination takes counts, not a response envelope — every backend spells its pagination differently, and a component that understands one framework's meta is one the next app works around. Give it pageSize and onPageSizeChange for a rows-per-page select; changing the size changes which rows are on which page, so go back to page 1 yourself.

    Pagination with a rows-per-page select

  • TableToolbar holds TableSearch, TableSortMenu and TableFilters. Like Table, they report what the user asked for and change no rows: keep their values in the state that builds your query, and reset to page 1 when they change.

    Table toolbar with the filters open

    Table toolbar with the sort menu open

  • Badge takes a semantic tone, not a status. Map your own vocabulary onto the five tones at the call site and fall back to neutral for anything unrecognised, so an unfamiliar status is still displayed rather than dropped. (WorkspaceBadge is the decorative initial beside a workspace name; it is aria-hidden and is not this.)

  • Alert is for a message that belongs to the content beside it: a failed save, a warning above a form. It is not a page state (use EmptyState or ErrorState for a whole view) and not a toast. Errors and warnings are announced immediately, the rest politely.

    Alert tones in light mode

    Alert tones in dark mode

  • ProgressBar clamps its value to 0-100 and reads NaN as 0, so dividing bytes by a total that has not arrived yet is safe. Image and Poster show a placeholder at their fixed ratio when the image is missing or fails to load, so the layout does not jump.

    Progress bar and image states

  • FileUpload is a drop zone and a button, and nothing more: it hands you the first file picked or dropped and does no network work. Run the transfer yourself, then feed back uploading and error. accept uses the input attribute's syntax and is checked on drop too, where the browser does not enforce it. There is no Chip: a static label is a neutral Badge.

    File upload drop zone

  • DonutChart is plain SVG drawn from the kit's tokens, so it adds no charting dependency. Give the done part of a progress ring a tone and the remainder muted, or leave tones off for a breakdown. Negative and non-numeric values count as zero. It has no hover or click: the label already tells a screen reader every segment's value, and the optional legend is hidden from assistive tech for that reason.

    Donut chart showing progress

    Donut chart breakdown with a legend

  • toUtcDateValue / fromUtcDateValue convert between a UTC timestamp and the YYYY-MM-DD value DatePicker takes. Reading the UTC date rather than the local one is what stops a stored midnight-UTC date showing as the day before for anyone behind UTC.

  • LoadingState / EmptyState / ErrorState are three renders, never one. "There is nothing here" and "we could not find out" lead to opposite actions. ErrorState takes a message string rather than an error object: turning a failure into a sentence needs to know which status codes your API uses for "come back later" versus "this is gone", which the kit cannot know.

Choosing which state to show is the caller's job — that depends on a data layer this kit has no opinion about.

Local kit work

Node 24 (see .nvmrc) — nvm use picks it up. The build toolchain needs Node 20.19+ or 22.12+; on anything older the build dies with a confusing node:util / styleText module error rather than a version complaint.

nvm use
npm install
npm test
npm run catalog:check
npm run catalog

The catalogue in catalog/ documents this package. It is not the Files demo.

Releasing

Releases are published from CI by .github/workflows/release.yml using npm Trusted Publishing (OIDC). There is no NPM_TOKEN secret — npmjs.com is configured to trust this repo's release.yml workflow.

Provenance is off (publishConfig.provenance). Trusted publishing turns it on by default, and npm does not accept provenance from a private source repository, which this one is — so leaving it on fails the publish outright. Turn it back on if the repo ever goes public.

To cut a release, bump version in package.json as part of the PR. Merging to staging runs typecheck, tests and the build, publishes, and tags the commit v<version>.

A merge that did not bump the version publishes nothing: the workflow asks npm whether that version exists and stops if it does, so an ordinary merge is a no-op rather than a failed publish. Pushing a v* tag by hand still works and is checked against package.json.

Stack for consuming apps

React + Vite. TanStack Query for all server data. Do not add raw fetch / useEffect data loading in the apps.

版本与发布记录

@cinesend/ui-kit 共发布 11 个版本,首次发布于 2026/09/26;过去 12 个月发布了 11 个版本。

版本发布日期距今
0.5.5latest2026/11/036 天前
0.5.42026/11/036 天前
0.5.32026/11/036 天前
0.5.22026/11/027 天前
0.5.12026/10/3010 天前
0.5.02026/10/2911 天前

关于 @cinesend/ui-kit 的常见问题

@cinesend/ui-kit 是什么?

Shared React components, design tokens, and CSS for CineSend apps.

如何安装 @cinesend/ui-kit?

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

@cinesend/ui-kit 支持 TypeScript 吗?

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

@cinesend/ui-kit 支持 ESM 还是 CommonJS?

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

@cinesend/ui-kit 可以商用吗?

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

@cinesend/ui-kit 的下载量有多少?

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

@cinesend/ui-kit 还在维护吗?

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