Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7160347
refactor: move tests out of src and trim the harness
YaelAnaya Sep 9, 2026
aaf5b21
feat(theme): semantic status and overlay tokens; close the colour nam…
YaelAnaya Sep 9, 2026
6f3862a
refactor(theme): use each library's own state attributes; delete tail…
YaelAnaya Sep 9, 2026
3f745b8
fix: Button defaults to type=button; submenus leave on dismissal; beh…
YaelAnaya Sep 9, 2026
a1a49b8
refactor: radii and type sizes from the scale; 0.3.0 metadata
YaelAnaya Sep 9, 2026
06f0922
test: verify the packed package as a real Tailwind consumer
YaelAnaya Sep 9, 2026
7ec7e5a
docs: visual catalog under examples/catalog
YaelAnaya Sep 9, 2026
d1bf071
docs: DESIGN.md matches the implementation; migration guide; CI runs …
YaelAnaya Sep 9, 2026
b8d1e53
feat: Sidebar and useIsMobile from the shadcn registry; @/ alias in t…
YaelAnaya Sep 10, 2026
1f46654
feat: built on top of shadcn/ui — vendored utility layer, components.…
YaelAnaya Sep 10, 2026
ae7454b
feat: Sidebar exported, tested and catalogued; 0.4.0 docs say built o…
YaelAnaya Sep 10, 2026
563c3ab
wip: snapshot completo (temporal, se reescribe en ramas por propósito)
YaelAnaya Sep 20, 2026
a8ea966
feat: eighteen components from the shadcn registry; forty in the package
YaelAnaya Sep 20, 2026
ec597e4
feat: the catalog becomes a documentation site, on Astro
YaelAnaya Sep 21, 2026
9bc02ca
refactor!: drop the token mirror, leaving the stylesheet as the only …
YaelAnaya Sep 21, 2026
92283a1
Revert "wip: snapshot completo"
YaelAnaya Sep 21, 2026
cdd4a95
Merge pull request #6 from Robomous/feat/components-0.4.0
YaelAnaya Sep 21, 2026
016758b
Merge pull request #7 from Robomous/feat/docs-site-astro
YaelAnaya Sep 21, 2026
74cbe49
Merge pull request #8 from Robomous/refactor/drop-token-mirror
YaelAnaya Sep 21, 2026
23de854
feat(theme): font-mono is Geist Mono, bundled beside the sans face
YaelAnaya Sep 22, 2026
95465a1
docs: drop the last references to the token mirror
YaelAnaya Sep 22, 2026
497d7b7
feat: five form and disclosure components, an icons subpath, status s…
YaelAnaya Sep 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
13 changes: 10 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,24 @@ on:
jobs:
checks:
runs-on: ubuntu-latest
env:
ASTRO_TELEMETRY_DISABLED: "1"
steps:
- uses: actions/checkout@v7
# The pnpm version comes from package.json#packageManager, the same one
# that wrote the lockfile.
- uses: pnpm/action-setup@v6
with:
version: 11
- uses: actions/setup-node@v7
with:
node-version-file: .nvmrc
cache: pnpm
- run: pnpm install --frozen-lockfile
# The same sequence `pnpm verify` runs locally, one step each so a failure
# names its stage: format → lint + typecheck → behaviour tests → build →
# docs site → pack + consumer smoke.
- run: pnpm format:check
- run: pnpm lint
- run: pnpm build
- run: pnpm test
- run: pnpm build
- run: pnpm docs:check
- run: pnpm test:package
7 changes: 4 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ jobs:
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
with:
version: 11
# No registry-url: setup-node would write an .npmrc auth-token
# placeholder that preempts npm's OIDC trusted-publishing exchange.
- uses: actions/setup-node@v7
with:
node-version-file: .nvmrc
- run: pnpm install --frozen-lockfile
- run: pnpm build
# Nothing publishes that has only been compiled: format, lint, typecheck,
# behaviour tests, build, the docs site, and the packed-consumer smoke test all
# have to pass first.
- run: pnpm verify
- name: tag matches package.json
run: |
test "v$(node -p "require('./package.json').version")" = "$GITHUB_REF_NAME"
Expand Down
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
node_modules/
dist/
examples/catalog/dist/
examples/catalog/.astro/
examples/catalog/src/generated/
*.tmp.md

# The Claude Design sync: its inputs, its staged converter and the bundle it
# produces. Local tooling, not part of the package.
.design-sync/
.ds-sync/
ds-bundle/
12 changes: 12 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Build output and generated files.
dist/
examples/catalog/.astro/
examples/catalog/src/generated/
pnpm-lock.yaml

# Markdown is formatted by hand here, and Prettier's markdown printer fights
Expand All @@ -17,6 +19,16 @@ pnpm-lock.yaml
# 100-column guide in `.editorconfig`.
*.md

# MDX pages are prose too, hand-formatted for the same two reasons.
*.mdx

# Process records, kept as written at the time.
docs/superpowers/
.superpowers/

# Vendored byte for byte from shadcn/dist/tailwind.css; tests/theme/shadcn.test.ts
# compares the two, so a reformat here would read as drift.
src/theme/shadcn.css

# Local tool state, not part of the repository.
.claude/
442 changes: 0 additions & 442 deletions DESIGN.md

This file was deleted.

82 changes: 61 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

# @robomous/ui-core

The Robomous design system: twenty-one React components this package owns outright, the design
tokens they resolve through, and the gates that hold the rules the design system is made of.
Extracted from [Robomous/VisionSet](https://github.com/Robomous/VisionSet), where it grew up.
The Robomous design system, built on top of [shadcn/ui](https://ui.shadcn.com): forty-five React
components this package owns outright, over Radix UI and Base UI behaviour — plus cmdk under
`Command` and vaul under `Drawer` — and the one stylesheet they resolve through. Extracted from
[Robomous/VisionSet](https://github.com/Robomous/VisionSet), where it grew up.

## Install

Expand All @@ -21,30 +22,69 @@ Peer dependencies: `react` ≥ 19, `react-dom` ≥ 19, `tailwindcss` ≥ 4.
```ts
import "@robomous/ui-core/styles.css"; // once, in the app's entry
import { Button, Card } from "@robomous/ui-core";
import { CheckIcon } from "@robomous/ui-core/icons"; // lucide-react, the set the components use
```

The stylesheet is the contract: Tailwind v4 reads its `@theme` block, so `bg-primary` in a
component here and `bg-primary` in a consuming app are the same colour by construction. A
consumer adds its own extensions in its own stylesheet after importing this one.
`@robomous/ui-core/icons` re-exports all of lucide-react at the version the components are built
against, so an app draws its own icons from the same set without declaring a second copy. It is a
subpath because five lucide names (`Badge`, `Command`, `Sheet`, `Sidebar`, `Table`) are also
components; prefer the `…Icon` spelling. One imported icon bundles as one icon.

## Adding a component
The stylesheet is the contract. Tailwind v4 reads its `@theme` block, so `bg-primary` in a
component here and `bg-primary` in a consuming app are the same colour by construction. Components
paint with roles (`primary`, `muted`, `success`, `warning`, `info`, `destructive`, `overlay`, …).
Underneath, Tailwind's palette is trimmed to eighteen scales — `neutral`, which the grey roles are
spelled from, and seventeen hues for data such as chart series — while `slate`, `gray`, `zinc`,
`stone`, the tinted neutrals, `white` and `black` produce nothing. A consumer adds an
`@source` for its own sources after the import, and declares any extension of its own in its own
stylesheet.

Write it. A component is a file in `src/components/` that wraps Radix or Base UI behaviour,
spells colour only through the tokens, keeps its own geometry, and is exported by name from
`src/index.ts`. [DESIGN.md](DESIGN.md) has the checklist and the reasoning behind each item; the
gates hold the parts of it that a review would otherwise have to catch by eye.
## Built on shadcn/ui

## The gates
A component enters this package from the shadcn registry and is owned here from then on. It is
not an extension of shadcn: the registry is where a component starts, `components.json` is how it
arrives, and the rules in `docs/DESIGN.md` are what it is adapted to before it is exported.

The rules that keep the design system honest are published alongside it:
`import { ... } from "@robomous/ui-core/gates"` gives a consumer the same eight pure scan helpers
and token facts this repo's own gates run — no colour in a class string, one status palette with
one home, no rival colour family standing in for it, brand as identity only, and the token names
read off the shipped stylesheet so a consumer can prove none of its own extensions shadows one.
The rules themselves, and which gate holds each, are in [DESIGN.md](DESIGN.md).
```
pnpm dlx shadcn@latest add <name>
```

writes `src/components/<name>.tsx` (and a hook under `src/hooks/` if the item brings one) with
`@/` imports that the build resolves. The adaptation checklist follows in DESIGN.md, *Adding a
component*. An existing component is never reinstalled.

shadcn's utility and variant layer — `data-open:`, `data-active:` and the rest, `no-scrollbar`,
`scroll-fade-*`, `shimmer-*` — ships inside the stylesheet as `src/theme/shadcn.css`, a
byte-for-byte copy of `shadcn/tailwind.css` held to the installed package by a test. It is a copy
because the stylesheet ships as source: a package named in it would become a runtime dependency of
every consumer, and `shadcn` is the CLI.

## Repository

```text
src/components/ the components, one file each
src/hooks/ the hooks the components are built on
src/theme/ styles.css, the single visual contract; shadcn.css, shadcn's layer
src/index.ts the public surface, exported by name
components.json how the shadcn CLI installs a new component here
tests/ behaviour tests, the token and shadcn-layer contracts, the packed-consumer test
examples/catalog the documentation site (Astro): a page, live demos and API tables per component
docs/ DESIGN.md, CONTRIBUTING.md, components/, MIGRATION-*.md
```

- [docs/DESIGN.md](docs/DESIGN.md) — the rules and how each one is held.
- [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) — scripts, adding a component or a token, releasing.
- [docs/components/README.md](docs/components/README.md) — what each component is for.
- [docs/MIGRATION-0.3.md](docs/MIGRATION-0.3.md) — moving from 0.2 to 0.3.

## Verification

`pnpm verify` runs format, lint and typecheck, the behaviour tests, the build, the docs site, and a
packed-consumer test that installs the tarball into a throwaway project and compiles it with a real
Tailwind. The release workflow runs the same before it publishes.

## Release

Bump `version` in `package.json`, commit, then tag `vX.Y.Z` and push the tag. CI publishes to
npm via trusted publishing (OIDC) — no tokens. The release workflow refuses a tag that does not
match `package.json`.
Bump `version` in `package.json`, commit, then tag `vX.Y.Z` and push the tag. CI verifies, then
publishes to npm via trusted publishing (OIDC) with provenance — no tokens. The workflow refuses a
tag that does not match `package.json`.
25 changes: 25 additions & 0 deletions components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "radix-nova",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/theme/styles.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"rtl": false,
"aliases": {
"components": "@/components",
"ui": "@/components",
"hooks": "@/hooks",
"lib": "@/lib",
"utils": "@/lib/utils"
},
"menuColor": "default",
"menuAccent": "subtle",
"registries": {}
}
106 changes: 106 additions & 0 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Contributing

## Setup

Node 24 (`.nvmrc`) and the pnpm named in `package.json#packageManager`.

```
pnpm install
```

The repository is a pnpm workspace: the package at the root, and the documentation site (Astro) at
`examples/catalog` as a second member that depends on it as `workspace:*`.

## Scripts

| Script | What it does |
| --- | --- |
| `pnpm test` | The behaviour tests and the token contract, under jsdom (vitest project `unit`). |
| `pnpm test:package` | Builds, packs, installs the tarball into a temporary consumer, compiles it with a real Tailwind, imports it under Node and bundles it. Slow; touches the registry. |
| `pnpm lint` | ESLint (including the colour-literal rule) and `tsc --noEmit` over `src` and `tests`. |
| `pnpm format` / `pnpm format:check` | Prettier. Markdown is hand-formatted at 100 columns and excluded. |
| `pnpm build` | Cleans `dist/`, compiles `src/` with declarations, and rewrites the `@/` alias to relative paths (`tsc-alias`). |
| `pnpm docs:dev` | Stops any running dev server, drops Vite's dependency cache, builds the package and serves the docs site on <http://localhost:4321>. Named with the colon because plain `docs` is a pnpm built-in. |
| `pnpm docs:check` | Generates the API tables, runs `astro check` and builds the docs site, as CI does. |
| `pnpm verify` | Everything above in CI order: format, lint, test, build, docs site, package test. |

## Adding or changing a component

A new component usually comes from the shadcn registry:

```
pnpm dlx shadcn@latest add <name>
```

`components.json` tells the CLI to write into `src/components/` and `src/hooks/` with `@/`
imports, which the build resolves. Read the diff, then adapt: the checklist is in
[DESIGN.md](DESIGN.md), *Adding a component*. In short: colour only through the roles, geometry in
the component, `type="button"`, no exit animation on a surface whose trigger can be pressed again on
the next frame, export by name from `src/index.ts`, a behaviour test in `tests/components/`, a
page with demos in the docs site, a row in `docs/components/README.md`. Never reinstall an existing
component.

The CLI pulls in an item's registry dependencies, which for most of the newer components include
`button`, `input`, `dialog` and `input-group` — all of them already here and edited. It asks about
each one, and the prompt is interactive: under `CI=1` or a non-terminal stdin it hangs and the
batch stops half-written. Answer it from stdin and the run finishes:

```
yes n | pnpm dlx shadcn@latest add <names> --yes
```

Then check that the files it offered to overwrite are unchanged before reading the diff.

State styles are written in shadcn's variants — `data-open:`, `data-closed:`, `data-active:`,
`data-disabled:`, `data-horizontal:`, `data-vertical:` — never in the attribute a behaviour library
happens to emit (`data-[state=open]:`, `data-[orientation=vertical]:`). A `data-[…]` bracket is for
the values the layer declares no variant for, and for attributes that are not states; see
DESIGN.md, *State attributes*.

## The docs site

`examples/catalog` is an Astro app that imports the package through the workspace link, so
`pnpm build` runs first. A component page is `src/content/components/<name>.mdx`; its demos are
`src/demos/<name>/<story>.tsx`, one story per file, and the file is the code sample the page shows.
The API tables are generated into `src/generated/api/` (gitignored) by `scripts/extract-api.ts`
from the component sources: `astro dev` and `pnpm docs:check` run it, and
`tests/docs/api-extractor.test.ts` holds its shape and fails once a component has no page.
`.astro` files are formatted by Prettier but not linted; the colour-literal rule covers the `.tsx`
demos and islands.

## Updating the shadcn layer

`src/theme/shadcn.css` is a byte-for-byte copy of `shadcn/dist/tailwind.css`, and
`tests/theme/shadcn.test.ts` fails when the two differ. To take a new version:

```
pnpm up shadcn
cp node_modules/shadcn/dist/tailwind.css src/theme/shadcn.css
git diff src/theme/shadcn.css
```

Read that diff as a change to every consumer's CSS, because it is one. Nothing of ours goes in
that file; a utility we need lives in `styles.css` after the import.

## Adding a token

A token is a role. Declare it in `:root` and `.dark` in `src/theme/styles.css`, expose it in
`@theme inline` as `--color-<role>: var(--<role>)`, add it to `ROLE_NAMES` in
`tests/theme/tokens.test.ts`, and give it a swatch in
`examples/catalog/src/components/Swatches.astro`. If it is not a role — if the name describes a
pigment rather than a purpose — it does not belong here.

## Tests

Assert what a screen would lose: roles, focus, `aria-*`, what a click or a keypress does. Do not
pin class strings unless the class *is* the documented contract (a menu's `min-w-32` floor is one;
a shade is not). `tests/setup.ts` holds exactly the harness pieces a current test needs, each with
the test that needs it named.

## Releasing

Bump `version` in `package.json`, run `pnpm verify`, commit, tag `vX.Y.Z` and push the tag. The
release workflow runs `pnpm verify` again, refuses a tag that does not match `package.json`, and
publishes to npm through trusted publishing (OIDC) with provenance. No tokens. A change that
removes or renames a public export gets a `docs/MIGRATION-<version>.md` naming every consumer site
found and the edit each one needs.
Loading
Loading