@cinesend/ui-kit
v0.2.0
Published
Shared React components, design tokens, and CSS for CineSend apps.
Maintainers
Readme
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-kitimport { 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:
reactandreact-dom(>=18.2 <20)@phosphor-icons/react^2.1.10sonner^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.

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


Tablesorts nothing. A sortable header reports the sort it wants throughonSortChange; 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.captionis required: it is the table's accessible name and it is visually hidden. Without it a screen reader announces "table" and nothing else.onRowClickis a pointer shortcut, not the row's only route. Rows keep theirrowrole, 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"plustabIndexon 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.stickyHeaderneeds the height bound on the table root itself, which is whatclassNameis for:<Table stickyHeader className="h-[32rem]" … />Not on a wrapper around it.
.cs-tablecarriesoverflow-x, and when one axis is notvisiblethe other computes toauto— so.cs-tableis 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.
Paginationtakes counts, not a response envelope — every backend spells its pagination differently, and a component that understands one framework'smetais one the next app works around.Badgetakes a semantic tone, not a status. Map your own vocabulary onto the five tones at the call site and fall back toneutralfor anything unrecognised, so an unfamiliar status is still displayed rather than dropped. (WorkspaceBadgeis the decorative initial beside a workspace name; it isaria-hiddenand is not this.)LoadingState/EmptyState/ErrorStateare three renders, never one. "There is nothing here" and "we could not find out" lead to opposite actions.ErrorStatetakes 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 catalogThe 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.
