Skip to content

feat: five form and disclosure components, an icons subpath, status surfaces - #9

Closed
YaelAnaya wants to merge 22 commits into
mainfrom
simplify-0.3.0
Closed

YaelAnaya wants to merge 22 commits into
mainfrom
simplify-0.3.0

Conversation

@YaelAnaya

Copy link
Copy Markdown
Contributor

Summary

  • New components (40 → 45): Checkbox, Switch, Slider, Accordion, Collapsible, each with behavioural tests (tests/components/form-controls.test.tsx, tests/components/disclosure.test.tsx) and a catalog page with demos.
  • @robomous/ui-core/icons: a new subpath that re-exports lucide-react, so consumers (and the catalog, which drops its own lucide-react dependency) use the same icon set the components ship with.
  • Theme: colour roles are now spelled from Tailwind's neutral scale, and the palette is trimmed to eighteen scales. Status roles are now ink/surface pairs (--success / --success-surface, and the same for warning, info and destructive) that clear 4.5:1 contrast in both light and dark.
  • Badge and Sonner use the new status surfaces. Toasts turn on richColors, mapped to the roles.
  • Table: a bordered, rounded scroll frame with rules between columns and a quieter header.
  • font-mono is now Geist Mono, bundled beside the sans face, and the last references to the token mirror are gone from the docs.
  • README, DESIGN.md, the component docs and the theming and installation pages are updated to match.

Test plan

  • pnpm verify passes locally (format, lint, typecheck, unit tests, build, docs check and build, packed-package consumer test)

YaelAnaya and others added 22 commits September 8, 2026 19:01
Tests live under tests/ (components, theme, gates, harness) and vitest runs
two projects: unit (jsdom) and package (node, for the packed-consumer test to
come). tests/setup.ts keeps only what a current test needs: the drained
macrotask after cleanup, sonner's dismiss, and the pointer-capture and
scrollIntoView stubs. The FormData, createObjectURL and localStorage
workarounds came from VisionSet application code that no longer lives here.
…espace; remove gates

success, warning and info are roles in styles.css with light and dark values,
exposed as bg-/text-/border-success and friends; overlay names the scrim
behind a Dialog or Sheet. @theme resets --color-* so Tailwind's default
palette is not part of the vocabulary, and --color-brand is no longer
exposed: brand stays a CSS variable for identity only. chart-* tokens had no
consumer and are gone; sidebar-* stays because two product shells use it.

With the palette closed, statusPaletteIn, competingStatusPaletteIn and
brandUsagesIn have nothing left to catch, and colouredClassesIn is held by an
ESLint no-restricted-syntax rule instead. src/gates, the ./gates export and
statusTone.ts (STATUS_INK, TONE_FILL, TONE_BORDER, StatusTone) are removed;
docs/MIGRATION-0.3.md will carry the consumer migration.
…wind.css

Radix emits data-state and data-orientation, Base UI emits bare data-open,
data-closed and data-highlighted, and Tailwind's built-in data-* variant
already matches the bare form. So the Radix components spell
data-[state=open] and data-[orientation=horizontal], the combobox keeps
data-open, and the custom variant layer that papered over the difference is
gone with the scroll-fade, shimmer and accordion utilities nothing here used.
The one custom utility a component did use, no-scrollbar, is inlined at its
single call site.
…avioural tests

A native <button> inside a <form> submits by default, so a Cancel could post
the form; Button now renders type=button unless told otherwise, and leaves an
asChild element's own semantics alone. DropdownMenuSubContent drops its exit
animation to match the root surface: while one runs, Radix keeps the closed
surface mounted and the next press is swallowed.

Button, Dialog/Sheet, Field and DropdownMenu get their own test files, on
roles, focus, aria relationships and the form/pointer outcomes a screen would
silently lose. The menu tests give exit-animation utilities a real
animation-name and a live getComputedStyle, so a surface that lingers fails
the way it does in a browser. Field's tests state the actual contract: the
anatomy is the component's, the aria wiring is the call site's.
rounded-md replaces the min(var(--radius-md), Npx) expressions — identical at
the default radius — text-sm replaces text-[0.8rem], ring-3 replaces
ring-[3px], the tooltip arrow takes rounded-xs (2px), and the in-button-group
hooks go with the ButtonGroup that never existed. package.json: 0.3.0,
sideEffects limited to CSS so a Button-only import tree-shakes, and a verify
script that runs the whole pipeline in order.
tests/package/consumer.test.ts builds, packs and installs the tarball into a
throwaway consumer, compiles its stylesheet with @tailwindcss/cli, imports the
entry under Node, renders a Button and bundles a Button-only entry with Vite.
It asserts the component utilities only @source can have found, the semantic
roles in both themes, the absence of any physical palette or brand utility,
the shipped file list, sideEffects, and that sonner and Base UI stay out of a
Button bundle. It fails on the @source regression 0.2.0 shipped with.

Two things it caught on its first run: dist/ was never cleaned, so a pack
after a rename shipped stale files (build now removes dist first), and the
import-resolution simulation it replaces is deleted.
A pnpm workspace member that depends on @robomous/ui-core as workspace:*, so
it imports the real package through the real exports map. Vite plus
@tailwindcss/vite, one page: foundations (roles, status, structure, brand as
a variable, type, radius, focus), every component, and a state matrix, with a
light/dark toggle. It owns examples only; pnpm catalog runs it and
pnpm catalog:check typechecks and builds it in CI.
…the full pipeline

docs/DESIGN.md describes the architecture that exists: a closed colour
namespace of roles, status as semantic utilities, brand as a variable, one
state dialect per behaviour library, no exit animation on any menu surface,
and Field as anatomy whose aria wiring is the call site's. Every mention of
gates, scanners, statusTone and chart tokens is gone. docs/MIGRATION-0.3.md
names each removed API, every consumer site found in VisionSet and
robomous-cloud, and the edit each needs. CONTRIBUTING.md and
docs/components/README.md are new; README points at them.

CI runs format, lint, test, build, catalog and the packed-consumer test as
separate steps; the release workflow runs pnpm verify before npm publish and
keeps OIDC trusted publishing with provenance. pnpm comes from
package.json#packageManager, the version that wrote the lockfile.
…json, @/ alias resolved in dist

styles.css imports src/theme/shadcn.css, a byte-for-byte copy of
shadcn/dist/tailwind.css: the data-* variants the registry's components
qualify their state styles on (matching both [data-state=x] and the bare
attribute, excluding "false"), no-scrollbar, scroll-fade-* and shimmer-*.
Vendored rather than imported, because the stylesheet ships as source and a
package named in it is a runtime dependency of every consumer — and shadcn is
the CLI. tests/theme/shadcn.test.ts holds the copy to the installed package,
to git, and to devDependencies. cn-rtl-flip, which the registry names and
shadcn defines nowhere, is declared in styles.css.

components.json returns so `pnpm dlx shadcn@latest add <x>` installs new
components into src/components with @/ imports; tsconfig declares the alias,
tsc-alias rewrites it to relative paths with extensions in dist, and vitest
resolves it against src. The tsconfig path for react was not needed.
…n top of shadcn/ui

The Sidebar family and useIsMobile join the public surface. Its test covers
the trigger and the keyboard shortcut, the cookie, the controlled caller, the
data-active attribute (the reason shadcn's variant layer is load-bearing) and
the Sheet it becomes under the breakpoint; tests/setup.ts gains the
window.matchMedia jsdom lacks. The packed-consumer test now renders the
Sidebar under Node, refuses an @/ import in dist, expects shadcn's layer and
cn-rtl-flip in the compiled CSS, and shadcn absent from runtime dependencies.

README, DESIGN.md and CONTRIBUTING.md describe the package as built on top of
shadcn/ui: how a component arrives from the registry, the adaptation
checklist, the vendored layer and how to update it, and a State attributes
section that explains why the layer is back.
Attachment, Avatar, Breadcrumb, ButtonGroup, Command, ContextMenu, Drawer,
Empty, HoverCard, Item, Kbd, Pagination, Popover, RadioGroup, ScrollArea,
Spinner, Toggle and ToggleGroup enter through `components.json`, each adapted
to the rules in DESIGN.md and owned here from then on. cmdk arrives under
Command and vaul under Drawer, the first two behaviour libraries in the
package that are neither Radix nor Base UI.

Every one is exported by name from `src/index.ts`, has a behaviour test, a
specimen in the catalog and a row in `docs/components/README.md`.

The state-attribute rule is settled while they land: a state is written in
shadcn's variants (`data-open:`, `data-active:`) and never in the attribute a
library happens to emit, so Dialog and Combobox read alike. A `data-[…]`
bracket survives only where the layer declares no variant, and for attributes
that are not states. The exit-animation rule grows with the new surfaces:
ContextMenuContent and PopoverContent join the menus in leaving on the frame
they are dismissed, because their trigger can be pressed again on the next.
`examples/catalog` was one long page of specimens for manual inspection. It is
now a documentation site: a page per component with a lede, the library its
behaviour comes from, live demos, the usage a consumer copies, an anatomy tree
and an API table, plus Introduction, Installation, Theming and States.

Astro 7 with React islands and Tailwind v4. The site dogfoods the package: the
left navigation is the package's own Sidebar, search is a Command palette over
a build-time index, and the API tables are its Table. Demos live one story per
file under `src/demos/<name>/`, and the file itself is the code the "View Code"
panel shows, so a sample cannot drift from what it renders.

API tables are read from the components' source by `scripts/extract-api.ts`
with the TypeScript compiler API: cva variants with their defaults, declared
props, inherited props a component gives a default to. The JSON is generated,
never committed, and `tests/docs/api-extractor.test.ts` holds its shape and
fails the day a component has no page.

`pnpm catalog` and `pnpm catalog:check` become `pnpm docs:dev` and
`pnpm docs:check`; the colon is not decoration, `pnpm docs` is a pnpm built-in
and would shadow the script. `docs:dev` stops a running dev server and drops
Vite's dependency cache before building, because a package rebuilt under a live
server leaves the optimizer serving two incompatible bundles and every island
fails to hydrate.
…copy

`src/theme/tokens.ts` held `LIGHT_THEME`, `DARK_THEME`, `THEME` and `cssVar`: a
TypeScript copy of the values in `styles.css`, kept for a caller that could not
read CSS, and a test to hold the two in step. Nothing in the package imported
it, no consumer did either, and a second copy of the palette is a second thing
that can go stale.

The stylesheet is now the only copy. A caller that genuinely needs a value as a
string reads it from the document, which also answers for the theme the reader
is in; everywhere else `var(--role)` resolves it.

`tests/theme/tokens.test.ts` keeps the half that was never about the mirror:
every role declared in both themes and nothing else, the palette closed, one
`--color-<role>` per role, the radius scale derived from `--radius`, dark mode
keyed off the class, no focus geometry in the base layer. The dark-theme check
that compared against the mirror now asserts what the mirror could not — that
the theme repoints its roles rather than restating the light values.

BREAKING CHANGE: `LIGHT_THEME`, `DARK_THEME`, `THEME` and `cssVar` are no
longer exported. docs/MIGRATION-0.3.md section 6 has the replacement.
El commit revertido fue un error mío: la red de seguridad del troceado se
commiteó en esta rama en lugar de en una aparte, y se empujó con ella. Su
contenido vive, ya separado por propósito, en las tres ramas que abren los
PR #6, #7 y #8. El árbol vuelve a ser exactamente el de ae7454b.
feat: eighteen components from the shadcn registry; forty in the package
feat: the catalog becomes a documentation site, on Astro
refactor!: drop the token mirror, leaving the stylesheet as the only copy
`font-mono` used to resolve to whatever monospace the reader's machine
happened to have, which made an identifier or a commit hash look like a
different design system on every OS. It is now a declared token:

    --font-mono: "Geist Mono Variable", monospace;

imported from `@fontsource-variable/geist-mono` the same way the sans
face already is, so it ships offline and needs nothing of the consumer.

No base rule puts it on `code` or `pre`. Applying it stays a call-site
decision, because a `<code>` inside a sentence is usually prose about
code rather than code.

`tests/theme/tokens.test.ts` pins the value. The packed-consumer test
now names `font-mono` in the consumer's own markup and asserts the whole
chain: both faces ship as `@font-face`, and the compiled `.font-mono`
rule carries Geist Mono. Removing the token makes that test fail with
Tailwind's own default, which is how it was checked to bite.
`src/theme/tokens.ts` went away in 9bc02ca, but two lines still described
it. The stylesheet's own header promised a TypeScript mirror that no
longer exists, and DESIGN.md's verification table said
`tests/theme/tokens.test.ts` checks that "roles agree between the
stylesheet and its mirror" — it never did, and now there is nothing to
agree with. That suite's only input is `styles.css`, read as text.

Both now say what is true: a caller that needs a value as a string reads
it off the document with `getComputedStyle`, and the table lists what the
test actually asserts — both themes declaring every role and nothing
else, `.dark` repointing rather than repeating, the closed palette, the
fonts, every radius step, and the absent `:focus-visible` rule.

The remaining mentions are history and stay: docs/MIGRATION-0.3.md
records the removal, and the plans under docs/superpowers/ are the
record of when the mirror existed.
…urfaces

- Checkbox, Switch, Slider, Accordion and Collapsible join the package
  (forty-five components), with behavioural tests and catalog pages.
- `@robomous/ui-core/icons` re-exports lucide-react, so consumers and the
  catalog draw icons from the same set without a second dependency.
- Theme: colour roles are spelled from Tailwind's neutral scale; status
  roles become ink/surface pairs (`--success` + `--success-surface`, etc.)
  that clear 4.5:1 in both themes. The palette is trimmed to eighteen scales.
- Badge and Sonner toasts use the status surfaces; Table gets a bordered,
  rounded scroll frame with column rules and a quieter header.
- Docs, DESIGN.md and theming pages updated to match.
@YaelAnaya YaelAnaya closed this Sep 23, 2026
@YaelAnaya
YaelAnaya deleted the simplify-0.3.0 branch September 23, 2026 23:53
@YaelAnaya

Copy link
Copy Markdown
Contributor Author

Superseded by #10 — the head branch was renamed to feat/form-controls-and-status-surfaces.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant