docs: README HTMX section — React-derivation, maintenance workflow, validation
- Make React-derivation explicit: the HTMX theme is generated from components/ui/*.tsx; never edit dist/greyhaven.htmx.css by hand. - Fix stale CSS example: update @layer components → @layer utilities and show the :where() zero-specificity wrappers the generator now emits. - Add "Maintenance — what to do when React changes" with explicit commands for cva tweaks, new components, generator bugs, and token updates. - Point consumers to public/htmx.html as the 1:1 reference implementation and to GAPS.md for the generator edge-case log. - Scope section updated: call out the vanilla-JS bridge that ships with public/htmx.html for checkbox/switch/tabs/toggle-group, separate from the positioning-heavy components that still need Alpine. - Scripts Reference: add pnpm htmx-demo:build.
This commit is contained in:
70
README.md
70
README.md
@@ -253,20 +253,24 @@ pnpm build-storybook # Static build
|
||||
|
||||
The React components assume a React runtime. For HTMX, Django templates, Rails ERB, Go `html/template`, Astro SSR, or any other server-rendered stack, consume the design system via the auto-generated CSS layer.
|
||||
|
||||
**The HTMX theme is derived from React.** React's `components/ui/*.tsx` is the single source of truth: the generator AST-walks each component, extracts its `cva()` bindings and static `className` strings, and emits CSS rules keyed on the same `data-slot` / `data-variant` / `data-size` attributes React already sets. If a `.tsx` file doesn't use it, the HTMX layer doesn't have it. Never edit `dist/greyhaven.htmx.css` by hand.
|
||||
|
||||
### What you get
|
||||
|
||||
`dist/greyhaven.htmx.css` is generated from `components/ui/*.tsx` (AST walk over `cva()` configs + static `className` strings on `data-slot` elements). It contains ~300 `@layer components` rules, one per data-slot, with attribute selectors for variants and sizes.
|
||||
`dist/greyhaven.htmx.css` is ~100KB with ~300 rules emitted into `@layer utilities`, one per data-slot, with attribute selectors for variants and sizes. Selectors are wrapped in `:where()` for zero specificity so consumer `className` overrides still win (matching React + tailwind-merge behavior).
|
||||
|
||||
```css
|
||||
[data-slot="card"] { @apply bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm; }
|
||||
[data-slot="card-header"] { @apply grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6; }
|
||||
[data-slot="card-title"] { @apply leading-none font-semibold; }
|
||||
@layer utilities {
|
||||
:where([data-slot="card"]) { @apply bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm; }
|
||||
:where([data-slot="card-header"]) { @apply grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6; }
|
||||
:where([data-slot="card-title"]) { @apply leading-none font-semibold; }
|
||||
|
||||
[data-slot="button"] { @apply inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium …; }
|
||||
[data-slot="button"]:not([data-variant]),
|
||||
[data-slot="button"][data-variant="default"] { @apply bg-primary text-primary-foreground hover:bg-primary/90; }
|
||||
[data-slot="button"][data-variant="outline"] { @apply border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground; }
|
||||
[data-slot="button"][data-size="sm"] { @apply h-8 rounded-md gap-1.5 px-3; }
|
||||
:where([data-slot="button"]) { @apply inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium …; }
|
||||
:where([data-slot="button"]):where(:not([data-variant])) { @apply bg-primary text-primary-foreground hover:bg-primary/90; }
|
||||
:where([data-slot="button"]):where([data-variant="default"]) { @apply bg-primary text-primary-foreground hover:bg-primary/90; }
|
||||
:where([data-slot="button"]):where([data-variant="outline"]) { @apply border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground; }
|
||||
:where([data-slot="button"]):where([data-size="sm"]) { @apply h-8 rounded-md gap-1.5 px-3; }
|
||||
}
|
||||
```
|
||||
|
||||
### Install
|
||||
@@ -304,19 +308,58 @@ Add to your Tailwind v4 input CSS:
|
||||
<span data-slot="badge" data-variant="success">Active</span>
|
||||
```
|
||||
|
||||
See `public/htmx.html` for a full reference implementation — every component in the React showcase has a matching static-HTML section.
|
||||
|
||||
### Scope
|
||||
|
||||
- **Static visual components** (Card, Button, Badge, Input, Label, Textarea, Table, Separator, Code, Kbd, Progress, Avatar, Skeleton, Alert, Pagination, Breadcrumb, Navbar, etc.) → fully driven by CSS, no JS needed.
|
||||
- **Interactive components** (Dialog, Dropdown, Popover, Select, Combobox, Accordion, Tabs, Tooltip, etc.) → CSS emits their static styles, but open/close / positioning / focus management is the consumer's responsibility. Alpine.js pairs naturally with HTMX for these.
|
||||
- **Interactive components with simple state toggles** (Checkbox, Switch, Tabs, ToggleGroup) → `public/htmx.html` ships a ~60-line vanilla-JS delegation bridge that flips `data-state` on click. Copy or adapt it into your consumer.
|
||||
- **Interactive components with positioning** (Dialog, Dropdown, Popover, Select, Combobox, Tooltip) → CSS emits their per-state visual rules, but open/close, portaling, and focus management are the consumer's responsibility. Alpine.js pairs naturally with HTMX for these.
|
||||
- **Native HTML alternatives**: `<details>` covers Accordion/Collapsible, `<dialog>` covers Dialog. The CSS rules apply to those too.
|
||||
|
||||
### Regenerate
|
||||
### Maintenance — what to do when React changes
|
||||
|
||||
Because the HTMX layer is generated from React, **React is the only place to edit component styles**. Every consumer-facing change follows the same flow.
|
||||
|
||||
**When any `components/ui/*.tsx` changes** (tweaked cva, new variant, renamed slot):
|
||||
|
||||
```bash
|
||||
pnpm htmx-css:build # Regenerate dist/greyhaven.htmx.css from components/ui/*.tsx
|
||||
pnpm htmx-css:build # regenerate dist/greyhaven.htmx.css
|
||||
pnpm htmx-demo:build # recompile public/htmx.css (Tailwind over the new layer)
|
||||
```
|
||||
|
||||
Re-runs of `./skill/install.sh --htmx-css` in consumer projects refresh their copy.
|
||||
Then capture a screenshot diff to confirm nothing drifted:
|
||||
|
||||
```bash
|
||||
pnpm dev # serve React at /, HTMX mirror at /htmx.html
|
||||
# capture each section as -react.webp and -htmx.webp (via Charlotte or equivalent)
|
||||
bash htmx-demo/compare-all.sh # 22-section diff, threshold 99%
|
||||
```
|
||||
|
||||
**When a new component is added** (new `.tsx` with its own `data-slot`):
|
||||
|
||||
1. `pnpm htmx-css:build` — generator picks it up from the AST automatically.
|
||||
2. Add a mirror section to `public/htmx.html` with the right `data-slot` / `data-state` / `data-variant` / `data-size` attributes on static DOM.
|
||||
3. If the component needs state toggling, extend the vanilla JS bridge at the bottom of `public/htmx.html` (existing `checkbox` / `switch` / `tabs-trigger` / `toggle-group-item` branches are templates).
|
||||
4. Add the section name to `SECTIONS[]` in `htmx-demo/compare-all.sh`.
|
||||
5. Capture screenshots, run `compare-all.sh`, confirm ≥99%.
|
||||
|
||||
**When the generator emits something wrong** (padding lost, state rule missing, specificity issue):
|
||||
|
||||
Check `GAPS.md` first — most generator edge cases are already documented there with root cause + fix. If it's new, fix the generator (never patch the consumer), add a `GAPS.md` entry, and re-run the demo build + diff sweep.
|
||||
|
||||
**When tokens change** (`app/tokens/tokens-*.css`):
|
||||
|
||||
Consumers copy those files locally (they're not generated into `dist/`). Consumer bumps the commit hash in their import and re-runs their Tailwind build. No action inside this repo.
|
||||
|
||||
**Pushing changes to a consumer:**
|
||||
|
||||
```bash
|
||||
# In the consumer repo, after this design system has been updated:
|
||||
/path/to/greyhaven-design-system/skill/install.sh . --htmx-css
|
||||
# then re-run the consumer's Tailwind build, e.g.:
|
||||
npm run build:css
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -341,6 +384,7 @@ Re-runs of `./skill/install.sh --htmx-css` in consumer projects refresh their co
|
||||
| `pnpm tokens:build` | Regenerate CSS/TS/MD from token JSON files |
|
||||
| `pnpm skill:build` | Regenerate skill/SKILL.md and skill/AGENTS.md from tokens + catalog |
|
||||
| `pnpm htmx-css:build` | Regenerate dist/greyhaven.htmx.css from components/ui/*.tsx |
|
||||
| `pnpm htmx-demo:build` | Recompile public/htmx.css (Tailwind over the generated layer) for the HTMX showcase |
|
||||
| `pnpm mcp:start` | Start the MCP server (stdio transport) |
|
||||
| `pnpm mcp:build` | Type-check MCP server |
|
||||
| `pnpm lint` | Run ESLint |
|
||||
|
||||
Reference in New Issue
Block a user