From 80252baf534d8c9c6c8ec9a89e71c11bb378eeb7 Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 25 May 2026 19:32:22 +0200 Subject: [PATCH] version 0.2.0 replaces fromer version! --- BOOTSTRAP.md | 43 +- CHANGELOG.md | 72 ++- DesignSystemIntroduction.md | 225 ++++--- MultiFrameworkSupport.md | 418 ++++++++++++ README.md | 109 +++- SKILL.md | 35 +- adapters/django/README.md | 56 ++ adapters/django/templates/whynot/_banner.html | 6 + .../django/templates/whynot/_base_head.html | 9 + adapters/django/templates/whynot/_button.html | 6 + .../django/templates/whynot/_empty_state.html | 9 + .../django/templates/whynot/_eyebrow.html | 4 + .../django/templates/whynot/_field_row.html | 10 + .../django/templates/whynot/_page_header.html | 6 + .../django/templates/whynot/_pipeline.html | 4 + .../templates/whynot/_prototype_card.html | 12 + .../django/templates/whynot/_stage_dot.html | 4 + adapters/django/templates/whynot/_tag.html | 4 + examples/showcase/index.html | 347 ++++++++++ package.json | 49 +- playwright.config.mjs | 4 +- scripts/sync-shared-styles.mjs | 29 + src/components/Atoms.jsx | 102 --- src/components/Chrome.jsx | 165 ----- src/elements/_styles.js | 604 ++++++++++++++++++ src/elements/atoms.js | 164 +++++ src/elements/chrome.js | 206 ++++++ src/elements/form.js | 205 ++++++ src/elements/icons.js | 45 ++ src/elements/layout.js | 277 ++++++++ src/index.js | 49 +- src/styles/components.css | 590 +++++++++++++++++ tests/visual/ui-kit.spec.mjs | 44 +- 33 files changed, 3434 insertions(+), 478 deletions(-) create mode 100644 MultiFrameworkSupport.md create mode 100644 adapters/django/README.md create mode 100644 adapters/django/templates/whynot/_banner.html create mode 100644 adapters/django/templates/whynot/_base_head.html create mode 100644 adapters/django/templates/whynot/_button.html create mode 100644 adapters/django/templates/whynot/_empty_state.html create mode 100644 adapters/django/templates/whynot/_eyebrow.html create mode 100644 adapters/django/templates/whynot/_field_row.html create mode 100644 adapters/django/templates/whynot/_page_header.html create mode 100644 adapters/django/templates/whynot/_pipeline.html create mode 100644 adapters/django/templates/whynot/_prototype_card.html create mode 100644 adapters/django/templates/whynot/_stage_dot.html create mode 100644 adapters/django/templates/whynot/_tag.html create mode 100644 examples/showcase/index.html create mode 100644 scripts/sync-shared-styles.mjs delete mode 100644 src/components/Atoms.jsx delete mode 100644 src/components/Chrome.jsx create mode 100644 src/elements/_styles.js create mode 100644 src/elements/atoms.js create mode 100644 src/elements/chrome.js create mode 100644 src/elements/form.js create mode 100644 src/elements/icons.js create mode 100644 src/elements/layout.js create mode 100644 src/styles/components.css diff --git a/BOOTSTRAP.md b/BOOTSTRAP.md index e12c412..5eb7aff 100644 --- a/BOOTSTRAP.md +++ b/BOOTSTRAP.md @@ -1,6 +1,6 @@ # Bootstrap: prime `whynot-design` from this seed -You're holding a zip — `whynot-design-seed.zip` — that contains a complete first commit for the `whynot-design` repository. +You're holding a zip — `whynot-design-seed.zip` — that contains a complete first commit for the `whynot-design` repository at **v0.2.0**. ## Step-by-step @@ -13,22 +13,32 @@ rmdir whynot-design-seed # 2. Sanity-check the tree. ls -la -# Expect: README.md DesignSystemIntroduction.md SKILL.md CONTRIBUTING.md -# CHANGELOG.md package.json src/ tokens/ assets/ examples/ +# Expect: README.md DesignSystemIntroduction.md MultiFrameworkSupport.md +# SKILL.md CONTRIBUTING.md CHANGELOG.md BOOTSTRAP.md +# package.json src/ tokens/ assets/ adapters/ examples/ # .gitea/ .github/ scripts/ tests/ -# 3. First commit. +# 3. Replace placeholder host names. +# Search-and-replace `gitea.example.com` with your actual Gitea host in: +# - package.json +# - .npmrc +# - .gitea/workflows/ci.yml (and .github/ if you keep that) +# - README.md (one quick-start block) + +# 4. First commit. git add -A -git commit -m "feat: seed whynot-design from atelier — v0.1.0" -git tag v0.1.0 +git commit -m "feat: seed whynot-design at v0.2.0 — three-layer architecture, Lit web components" +git tag v0.2.0 git push origin main --tags -# 4. Verify the example renders. +# 5. Install + smoke-test. pnpm install -pnpm example -# Open http://localhost:3000 — should show the whynot-control kit. +pnpm showcase +# Visit http://localhost:4321/examples/showcase/ +# Every component should render. If Lit fails to load, +# check that `lit` ^3.2.1 resolved in node_modules. -# 5. (Optional) Generate Playwright baselines locally. +# 6. Generate Playwright baselines locally. pnpm exec playwright install --with-deps chromium pnpm test:visual:update git add tests/visual/__screenshots__ @@ -36,17 +46,18 @@ git commit -m "test: add initial visual-regression baselines" git push ``` +## After bootstrap + +1. **Record the bootstrap as `DEC-004` in `whynot-control/DECISIONS.md`** — something like *"Established whynot-design as the implementation surface, three-layer architecture, Lit web components as the canonical component layer."* +2. **Mention `whynot-design` in `whynot-control/SCOPE.md`** as a sibling repository. +3. **Add `@whynot/design` as a dependency in your first consuming app** — Django, React, or both. Follow `MultiFrameworkSupport.md` for the per-framework wiring. + ## Notes - The `git+ssh` URL in `package.json` (`gitea.example.com/whynot/whynot-design.git`) is a placeholder. Replace with your actual Gitea host. - The same goes for `.npmrc` and the registry URL in `.gitea/workflows/ci.yml` (commented out — uncomment when you stand up a Gitea Packages registry). - `.gitea/workflows/ci.yml` and `.github/workflows/ci.yml` are identical. Keep whichever your forge uses and delete the other. - -## After bootstrap - -1. Record the bootstrap as `DEC-004` in `whynot-control/DECISIONS.md`. -2. Mention `whynot-design` in `whynot-control/SCOPE.md` as a sibling repository (out of scope for `whynot-control`, in scope for the org). -3. Add `@whynot/design` as a dependency in your first consuming prototype to close the loop. +- The `examples/showcase/index.html` page uses `importmap` to load Lit from esm.sh **for the standalone-no-build case**. When you have a bundler in the consuming app, the bundler resolves `lit` from `node_modules` and the importmap is irrelevant. ## You can delete this file after bootstrap diff --git a/CHANGELOG.md b/CHANGELOG.md index ac44be2..b41cdc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,32 +2,72 @@ All notable changes to `@whynot/design` are recorded here. Hand-edited until release cadence makes it painful. -Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning rules: see [`DesignSystemIntroduction.md` §5](./DesignSystemIntroduction.md#5-versioning-discipline). +Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning rules: see [`DesignSystemIntroduction.md` §6](./DesignSystemIntroduction.md#6-versioning-discipline). ## [Unreleased] _Nothing yet. Add entries above the next `[vX.Y.Z]` block as PRs land._ +## [0.2.0] — 2026-05-25 + +**Architectural reframe.** The system is now delivered as three stacked layers — tokens + CSS, Lit web components, optional framework adapters. The previous React-only component layer has been removed. + +### Added + +- `MultiFrameworkSupport.md` — full integration guide for React, Django, HTMX, Vue, Svelte, plain HTML. +- `src/elements/` — Lit-based web components, light-DOM rendered: + - **Atoms** (`atoms.js`): ``, ``, ``, ``, ``, ``, ``. + - **Form** (`form.js`): ``, ``, ``, ``, ``. + - **Layout** (`layout.js`): ``, ``, `` + `` + ``, ``, `` + ``, ``, ``. + - **Chrome** (`chrome.js`): ``, `` + `` + ``, ``, ``, ``. +- `src/elements/icons.js` — Lucide-derived inline icon paths (no runtime CDN dependency). +- `src/styles/components.css` — utility-class layer (`wn-btn`, `wn-card`, `wn-tag`, etc.). Consumable directly from any HTML for the "Layer 1 only" use case. +- `adapters/django/templates/whynot/*.html` — `{% include %}`-ready partials for Django consumers (button, eyebrow, tag, stage-dot, page-header, pipeline, field-row, prototype-card, banner, empty-state). +- `adapters/django/README.md` — how to wire the partials into a Django app. +- `examples/showcase/index.html` — single-page reference rendering every component. Doubles as the Playwright visual-regression baseline. +- `lit` ^3.2.1 as a runtime dependency. + +### Changed + +- `DesignSystemIntroduction.md` — updated to describe the three-layer architecture, multi-framework consumption, and the revised propagation pipeline. +- `README.md` — top-level rewrite around the new architecture, with quick-start blocks per framework. +- `package.json` — adds per-group `exports` (`@whynot/design/atoms`, `/form`, `/layout`, `/chrome`, `/icons`). +- CI now runs visual regression against `examples/showcase/index.html` *and* `examples/whynot-control/index.html`. + +### Removed + +- `src/components/Atoms.jsx`, `src/components/Chrome.jsx` — the React-only component layer. Consumers using these from v0.1.0 should swap to the corresponding custom elements (drop-in replacements; see `MultiFrameworkSupport.md` § React). +- `peerDependencies` on React. React is no longer required to consume `@whynot/design`. + +### Migration + +If you were on v0.1.0 with React imports like `import { Button } from "@whynot/design"`: + +| Before (v0.1.0) | After (v0.2.0) | +|---|---| +| `import { Button } from "@whynot/design"; ` | `import "@whynot/design"; ` | +| `import { Tag } from "@whynot/design"; ` | `` | +| `` | `` | + +CSS imports are unchanged; add the new `components.css` import alongside `colors_and_type.css`. + +### Known caveats + +- IBM Plex is loaded from Google Fonts. Drop `.woff2` files into `fonts/` and swap to a local `@font-face` for offline use. +- The showcase uses `importmap` + esm.sh to load Lit (no bundler). Real consumers using `pnpm add` will pick up Lit from `node_modules` via the package's `dependencies` entry. +- No TypeScript declarations for JSX yet — deferred until a TS consumer asks. +- `` uses a native `` participates in `
` submission naturally. No ElementInternals contortions. +4. **HTMX swaps just work.** When HTMX replaces a fragment of the page with new HTML from the server, any `` elements in the new fragment are upgraded automatically by the browser's `connectedCallback`. + +The trade-off you give up is style isolation. For a design system, that's the right trade — the system *wants* its CSS to apply everywhere. + +--- + +## Per-framework setup + +### Plain HTML + +The minimum viable setup. No build step. + +```html + + + + + + + + + + Concierge prototype triage + Request invite + + + +``` + +Vendor the three files (`colors_and_type.css`, `components.css`, `index.js`) into your static directory. That's the whole integration. + +--- + +### Django (server-rendered templates + HTMX) + +This is the canonical non-React case and worth covering in detail. + +#### 1. Install + +```sh +# In the Django app's repo. +pnpm add git+ssh://git@gitea.example.com/whynot/whynot-design.git#v0.2.0 + +# Or vendor without Node tooling — copy three files: +mkdir -p myapp/static/whynot +cp node_modules/@whynot/design/src/styles/colors_and_type.css myapp/static/whynot/ +cp node_modules/@whynot/design/src/styles/components.css myapp/static/whynot/ +cp node_modules/@whynot/design/dist/index.bundle.js myapp/static/whynot/ +``` + +A small `Makefile` or `package.json` script makes this idempotent — re-run after every dependency bump: + +```makefile +sync-whynot: + cp node_modules/@whynot/design/src/styles/*.css myapp/static/whynot/ + cp node_modules/@whynot/design/dist/*.js myapp/static/whynot/ +``` + +#### 2. Wire it into your base template + +```django +{# templates/base.html #} +{% load static %} + + + + … + + + + + + {% block content %}{% endblock %} + + +``` + +That's it. From now on, **any Django template** can use `` elements with no further setup. + +#### 3. Use components in templates + +```django +{# templates/prototypes/detail.html #} +{% extends "base.html" %} + +{% block content %} + + +
+ + Prototypes + {{ prototype.id }} + + + + {{ prototype.pitch }} + + Park + + Promote → {{ prototype.target }} + + + + + + + + {{ prototype.learning_question }} + + + {{ prototype.smallest_test }} + + + {{ prototype.risks }} + +
+{% endblock %} +``` + +No `{% include %}` files needed for this to work. The components are *real HTML*; Django renders them like any other tag. + +#### 4. HTMX fragment swaps + +When HTMX swaps a fragment into the page, the new HTML contains `` elements. The browser upgrades them automatically — no extra wiring. + +```django +{# views.py #} +def signal_create(request): + if request.method == "POST": + sig = Signal.objects.create(…) + return render(request, "signals/_row.html", {"signal": sig}) # fragment + return render(request, "signals/_form.html") + +{# templates/signals/_row.html — the fragment HTMX swaps in #} + + {{ signal.id }} + + {{ signal.what }} + +``` + +```django +{# Where HTMX swaps it in #} + + … + +``` + +The new row's `` is upgraded on insertion. No `htmx:afterSettle` listener needed. + +#### 5. Django partials (optional Layer 3) + +If a particular component pattern recurs and the raw HTML is verbose, ship a `{% include %}` partial: + +```django +{# adapters/django/templates/whynot/_prototype_card.html — vendored in your project #} + + {{ p.pitch }} + {{ p.learning }} + {{ p.test }} + → {{ p.target }} + +``` + +Then: + +```django +{% include "whynot/_prototype_card.html" with p=prototype %} +``` + +`whynot-design` ships a starter set of these in `adapters/django/templates/whynot/`. **Copy them into your Django app's templates dir; don't add `adapters/django/templates/` to `TEMPLATES.DIRS` directly** — that would couple your `INSTALLED_APPS` to the design system's repo layout. Treat the partials as starting templates you can customise per-app. + +#### 6. Form participation + +``, ``, `` each wrap a real native input. Django's form rendering works as expected: + +```django +
+ {% csrf_token %} + + + + Save +
+``` + +The `name` attribute reaches the native input inside; the form submits normally. + +--- + +### React (Next.js, Vite, CRA, Remix) + +React supports custom elements natively since v19; in v18 the support is mostly fine for the common cases (attributes-not-properties, no event handlers via props). The whynot components are designed against the v18 baseline. + +#### 1. Install + +```sh +pnpm add @whynot/design +``` + +#### 2. Wire it into your app entry + +```jsx +// app/layout.jsx (Next.js) or src/main.jsx (Vite) +import "@whynot/design/styles/colors_and_type.css"; +import "@whynot/design/styles/components.css"; +import "@whynot/design"; // registers all custom elements +``` + +#### 3. Use components in JSX + +```jsx +export default function PrototypeDetailPage({ prototype }) { + return ( +
+ + {prototype.pitch} + + promote(prototype)}> + Promote + + + + + + {prototype.learningQuestion} + +
+ ); +} +``` + +#### 4. React-specific notes + +- **Attributes vs properties.** React passes string-typed props as attributes, which is what whynot components expect for all public APIs (`variant`, `signal`, `active-idx`). Boolean attributes work too (``). +- **Events.** React 18 doesn't bind `onMyEvent`-style props to custom-element events. Use `useEffect` + `addEventListener` for component-emitted events: + ```jsx + const ref = useRef(); + useEffect(() => { + const el = ref.current; + el?.addEventListener("wn-dismiss", handleDismiss); + return () => el?.removeEventListener("wn-dismiss", handleDismiss); + }, []); + return Saved.; + ``` + React 19 supports `onWnDismiss` directly. +- **JSX typing.** If you use TypeScript, add a `whynot.d.ts` declaring the custom elements as JSX intrinsic elements. `@whynot/design` does not ship this yet (deferred — see CHANGELOG). + +#### 5. Typed React wrappers (Layer 3, optional) + +If your team prefers `` over ``, one-line wrappers are trivial: + +```jsx +// myapp/lib/wn.jsx +export const WnButton = (props) => ; +export const WnCard = (props) => ; +``` + +Whether `whynot-design` ships these officially is on the deferred list — most React teams find them unnecessary once the IDE handles the custom-element completion. + +--- + +### Vue, Svelte, SolidJS + +All work out of the box; custom elements are platform features, not framework features. + +```vue + + +``` + +```svelte + +Promote +``` + +Vue may warn about unknown elements unless you configure `compilerOptions.isCustomElement` to recognise `wn-`-prefixed tags. One-line vite plugin config. + +--- + +## Server-side rendering specifics + +### When SSR matters + +For Django (or any server-rendered framework), `` reaches the browser as inert markup. It's *upgraded* — gains interactive behaviour — when the browser parses `index.js` and runs `customElements.define()`. Between those two moments, the user may see: + +- **The button** — fully styled by `components.css`, looking correct. +- **Click behaviour** — depends on the component. Pure-visual components (Eyebrow, Card, Tag) have no behaviour to gain. Interactive components (Modal, Toast, Select) need the upgrade. + +### Strategies for behaviour-during-load + +1. **Defer behaviour-required interaction.** A `` that's initially closed doesn't care about the upgrade window. Only when the user clicks "Open" does the upgrade matter — and by then `index.js` has loaded. +2. **Block on the module if it's truly critical.** Remove `defer` from the script tag for a smaller-than-50kb runtime — acceptable. Or use `type="module"` (which defers by default but blocks `DOMContentLoaded`). +3. **Use `noscript` fallbacks.** Components like `` already work as inert static HTML if JS never loads. Add `