diff --git a/CLAUDE.md b/CLAUDE.md index dd0066623..2545486e5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,6 +8,8 @@ React component library for **Bulma v1** in TypeScript. pnpm monorepo orchestrat - `bestax-migrate/` — the `bestax-migrate` codemod CLI (has its own CLAUDE.md) - `bestax-mcp/` — the `bestax-mcp` MCP server; its `data/` index is **generated** (has its own CLAUDE.md) +- `eslint-plugin/` — `@allxsmith/eslint-plugin-bestax`, lint rules for the library; + its `src/generated/` metadata is **generated** (has its own CLAUDE.md) - `skills/` — Agent Skills, a **shipped product** bundled into create-bestax (has its own CLAUDE.md) - `telemetry-worker/` — Cloudflare Worker ingesting the CLIs' opt-in telemetry (deployed from CI by `deploy-worker.yml` — a merged change under it ships to @@ -20,6 +22,8 @@ React component library for **Bulma v1** in TypeScript. pnpm monorepo orchestrat - `scripts/gen-component-catalog.mjs` — generates the skill component catalog (`pnpm gen:catalog`) - `scripts/gen-mcp-index.mjs` — generates the MCP server's data index (`pnpm gen:mcp`) - `scripts/gen-skills-rosters.mjs` — writes the skill install rosters from `skills/` (`pnpm gen:skills`) +- `scripts/gen-eslint-meta.mjs` — generates the ESLint plugin's component metadata + (`pnpm gen:eslint-meta`) ## Toolchain @@ -29,8 +33,8 @@ Node 22 locally (`.nvmrc`; CI runs Node 24) and `pnpm@11.9.0` (pinned via `packa ## Commands ```bash -pnpm all # the pre-PR gate: build, typecheck, test+coverage, bundle:stats, lint, format:check, storybook build -pnpm test # jest (bulma-ui + create-bestax + bestax-migrate + bestax-mcp) +pnpm all # the pre-PR gate: build, typecheck, test+coverage, bundle:stats, conformance, the three staleness checks, lint, format:check, storybook build +pnpm test # jest (bulma-ui + create-bestax + bestax-migrate + bestax-mcp + eslint-plugin) pnpm test:coverage # coverage — thresholds live in each package's jest config (see below) pnpm lint # eslint pnpm typecheck # tsc --noEmit (each package's build program) @@ -39,7 +43,8 @@ pnpm format # prettier --write (format:check to verify; covers md/mdx to pnpm gen:catalog # regenerate the skills component catalog (CI fails if stale) pnpm gen:mcp # regenerate the MCP server's data index (CI fails if stale) pnpm gen:skills # regenerate the skill install rosters (conformance fails if stale) -pnpm gen # all four generators (api docs, catalog, MCP index, skill rosters) +pnpm gen:eslint-meta # regenerate the ESLint plugin's metadata (in `pnpm all`; no CI step yet) +pnpm gen # every generator (api docs, catalog, MCP index, skill rosters, eslint metadata) pnpm docs # Docusaurus dev server :3000 pnpm storybook # Storybook dev server :6006 pnpm exec turbo run test --filter=@allxsmith/bestax-bulma # scope any task to one package @@ -87,7 +92,7 @@ Conventional Commits, enforced by commitlint (husky `commit-msg` hook) and consu semantic-release. Two repo-specific rules: - Commits of type `feat|fix|perf|refactor|style|revert` **must** use a scope of `bulma-ui`, `docs`, - `create-bestax`, `bestax-migrate`, or `bestax-mcp` — an unscoped commit of any of these + `create-bestax`, `bestax-migrate`, `bestax-mcp`, or `eslint-plugin` — an unscoped commit of any of these scope-gated types is rejected (`RELEASE_SCOPES` in `commitlint.config.js` is the source of truth). One exception worth knowing: commitlint's default ignores skip git's own `Revert "…"` messages, so the hook cannot @@ -135,8 +140,8 @@ Full versioning details (breaking-change footers, tag formats): `VERSIONING.md`. and every miss granted the exemption. A workspace **sibling** in `dependencies` or `optionalDependencies` is separately a violation however the specifier is spelled (#537), and the only way through is a line in `SIBLING_RUNTIME_DEPS` — same declared shape — for a - package that depends on a sibling at runtime on purpose (#644: the three CLIs on - `@allxsmith/bestax-bulma`, the way bulma-ui declares `bulma`); the test holds that declaration + package that depends on a sibling at runtime on purpose (#644: the three CLIs and + the ESLint plugin on `@allxsmith/bestax-bulma`, the way bulma-ui declares `bulma`); the test holds that declaration to the real manifests, so a removed dependency cannot leave a standing exemption. ## Workflow diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 388bae385..c7cb95f06 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -254,7 +254,7 @@ Runs the real commit analysis + next-version calc, but publishes nothing: ```bash export GITHUB_TOKEN= # the github plugin needs it even in dry-run -for pkg in bulma-ui create-bestax bestax-migrate bestax-mcp; do +for pkg in bulma-ui create-bestax bestax-migrate bestax-mcp eslint-plugin; do ( cd "$pkg" && pnpm exec semantic-release --dry-run --no-ci ) done ``` @@ -357,7 +357,7 @@ The short version for contributors: ## Semantic Release & Publishing -We use [Semantic Release](https://semantic-release.gitbook.io/) to automate publishing of every package to npm: `bulma-ui` as [`@allxsmith/bestax-bulma`](https://www.npmjs.com/package/@allxsmith/bestax-bulma), plus [`create-bestax`](https://www.npmjs.com/package/create-bestax), [`bestax-migrate`](https://www.npmjs.com/package/bestax-migrate) and [`bestax-mcp`](https://www.npmjs.com/package/bestax-mcp). +We use [Semantic Release](https://semantic-release.gitbook.io/) to automate publishing of every package to npm: `bulma-ui` as [`@allxsmith/bestax-bulma`](https://www.npmjs.com/package/@allxsmith/bestax-bulma), plus [`create-bestax`](https://www.npmjs.com/package/create-bestax), [`bestax-migrate`](https://www.npmjs.com/package/bestax-migrate), [`bestax-mcp`](https://www.npmjs.com/package/bestax-mcp) and `eslint-plugin` as [`@allxsmith/eslint-plugin-bestax`](https://www.npmjs.com/package/@allxsmith/eslint-plugin-bestax). - Use [Conventional Commits](https://www.conventionalcommits.org/) to trigger releases — see [Commit Message Guidelines](#commit-message-guidelines). - **Packages version and release independently, keyed off the commit scope** — `feat(bulma-ui)` releases only bestax-bulma. See [`VERSIONING.md`](./VERSIONING.md). @@ -369,7 +369,7 @@ Publishing authenticates with npm via [OIDC trusted publishing](https://docs.npm For this to work, each published package must have a trusted publisher configured **once** on npmjs.com (Package → Settings → Trusted Publisher): -- Packages: `@allxsmith/bestax-bulma`, `create-bestax`, `bestax-migrate` and `bestax-mcp` — every publishable package, and a missing entry fails the publish _after_ the release commit and tag are pushed +- Packages: `@allxsmith/bestax-bulma`, `create-bestax`, `bestax-migrate`, `bestax-mcp` and `@allxsmith/eslint-plugin-bestax` — every publishable package, and a missing entry fails the publish _after_ the release commit and tag are pushed - Provider: **GitHub Actions** - Repository: `allxsmith/bestax` - Workflow: `ci.yml` @@ -396,8 +396,8 @@ commitlint via the husky `commit-msg` hook ([`commitlint.config.js`](./commitlin - **Format:** `(): ` — imperative subject, blank line, then an optional body with bullet points and context. - **Release types need a scope:** commits of type `feat`, `fix`, `perf`, `refactor`, `style` - or `revert` **must** use a scope of `bulma-ui`, `docs`, `create-bestax`, `bestax-migrate` - or `bestax-mcp` (repo-specific commitlint rule — the scope decides which package releases, + or `revert` **must** use a scope of `bulma-ui`, `docs`, `create-bestax`, `bestax-migrate`, + `bestax-mcp` or `eslint-plugin` (repo-specific commitlint rule — the scope decides which package releases, see [`VERSIONING.md`](./VERSIONING.md)). One correction worth knowing about `revert`: a scoped conventional `revert(): …` **releases nothing** — the only revert rule is commit-analyzer's default `{ revert: true, release: 'patch' }`, keyed on the angular diff --git a/README.md b/README.md index 62c32e6b8..2b25523b3 100644 --- a/README.md +++ b/README.md @@ -96,14 +96,15 @@ function Root() { ## 📦 What's in this repo -| Path | Package | What it is | -| ------------------------------------ | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | -| [`bulma-ui/`](bulma-ui/) | [`@allxsmith/bestax-bulma`](https://www.npmjs.com/package/@allxsmith/bestax-bulma) | The component library | -| [`create-bestax/`](create-bestax/) | [`create-bestax`](https://www.npmjs.com/package/create-bestax) | Project scaffolder — `npm create bestax@latest` | -| [`bestax-migrate/`](bestax-migrate/) | [`bestax-migrate`](https://www.npmjs.com/package/bestax-migrate) | Codemods for migrating existing apps from other React Bulma libraries | -| [`bestax-mcp/`](bestax-mcp/) | [`bestax-mcp`](https://www.npmjs.com/package/bestax-mcp) | MCP server — component props, examples and skills for AI coding agents | -| [`docs/`](docs/) | — | Docusaurus source of [bestax.io](https://bestax.io) | -| [`skills/`](skills/) | — | [Agent Skills](https://bestax.io/docs/skills/intro) for coding agents (also bundled into `create-bestax`) | +| Path | Package | What it is | +| ------------------------------------ | -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | +| [`bulma-ui/`](bulma-ui/) | [`@allxsmith/bestax-bulma`](https://www.npmjs.com/package/@allxsmith/bestax-bulma) | The component library | +| [`create-bestax/`](create-bestax/) | [`create-bestax`](https://www.npmjs.com/package/create-bestax) | Project scaffolder — `npm create bestax@latest` | +| [`bestax-migrate/`](bestax-migrate/) | [`bestax-migrate`](https://www.npmjs.com/package/bestax-migrate) | Codemods for migrating existing apps from other React Bulma libraries | +| [`bestax-mcp/`](bestax-mcp/) | [`bestax-mcp`](https://www.npmjs.com/package/bestax-mcp) | MCP server — component props, examples and skills for AI coding agents | +| [`eslint-plugin/`](eslint-plugin/) | [`@allxsmith/eslint-plugin-bestax`](https://www.npmjs.com/package/@allxsmith/eslint-plugin-bestax) | ESLint rules — catches helper-prop values the library silently drops | +| [`docs/`](docs/) | — | Docusaurus source of [bestax.io](https://bestax.io) | +| [`skills/`](skills/) | — | [Agent Skills](https://bestax.io/docs/skills/intro) for coding agents (also bundled into `create-bestax`) | The published packages are versioned and released independently. diff --git a/SECURITY.md b/SECURITY.md index f68ac7ab1..5aa2bde92 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,16 +4,23 @@ Security fixes land on the **latest release line only** — currently `@allxsmith/bestax-bulma` 5.x, `create-bestax` 4.x, `bestax-migrate` 2.x, and -`bestax-mcp` 1.x. All four packages release automatically from `main` -(semantic-release), so the latest published version is always the patched one. +`bestax-mcp` 1.x. Those release automatically from `main` (semantic-release), +so the latest published version is always the patched one. Older majors may still work but receive no security updates; please upgrade. -| Package | Supported | Unsupported | -| ------------------------- | ------------ | ----------- | -| `@allxsmith/bestax-bulma` | 5.x (latest) | < 5.0 | -| `create-bestax` | 4.x (latest) | < 4.0 | -| `bestax-migrate` | 2.x (latest) | < 2.0 | -| `bestax-mcp` | 1.x (latest) | — | +`@allxsmith/eslint-plugin-bestax` has no supported line yet. The only version +on the registry is a `0.0.0-development` placeholder, published by hand to +claim the name so a trusted publisher could be attached to it, and it carries +no provenance because no laptop publish can. Its supported line starts at the +first release from `main`. + +| Package | Supported | Unsupported | +| --------------------------------- | -------------------- | ----------- | +| `@allxsmith/bestax-bulma` | 5.x (latest) | < 5.0 | +| `create-bestax` | 4.x (latest) | < 4.0 | +| `bestax-migrate` | 2.x (latest) | < 2.0 | +| `bestax-mcp` | 1.x (latest) | — | +| `@allxsmith/eslint-plugin-bestax` | none yet (see below) | — | ## Supply-Chain Security diff --git a/VERSIONING.md b/VERSIONING.md index 5d7ff6de0..b0f696345 100644 --- a/VERSIONING.md +++ b/VERSIONING.md @@ -1,14 +1,16 @@ # Independent Versioning Strategy -`@allxsmith/bestax-bulma`, `create-bestax`, `bestax-migrate`, and `bestax-mcp` are versioned +`@allxsmith/bestax-bulma`, `create-bestax`, `bestax-migrate`, `bestax-mcp`, and +`@allxsmith/eslint-plugin-bestax` are versioned and released **independently**. Each package releases only when a commit is scoped to it — the version numbers are unrelated (e.g. bestax-bulma 5.x alongside create-bestax 3.x). The source of truth is the `releaseRules` in each package's semantic-release config: [`bulma-ui/release.config.js`](./bulma-ui/release.config.js), [`create-bestax/release.config.js`](./create-bestax/release.config.js), -[`bestax-migrate/release.config.js`](./bestax-migrate/release.config.js), and -[`bestax-mcp/release.config.js`](./bestax-mcp/release.config.js). +[`bestax-migrate/release.config.js`](./bestax-migrate/release.config.js), +[`bestax-mcp/release.config.js`](./bestax-mcp/release.config.js), and +[`eslint-plugin/release.config.js`](./eslint-plugin/release.config.js). ## Release Rules @@ -16,23 +18,25 @@ A commit releases **only** the package its scope names. Representative examples `feat`/`fix`/`perf`/`refactor`/`style` and `BREAKING CHANGE:` rules apply to every package through its own scope: -| Commit | bestax-bulma | create-bestax | bestax-migrate | bestax-mcp | -| ----------------------------------------------------------------- | ------------ | ------------- | -------------- | ---------- | -| `feat(bulma-ui): …` | minor | — | — | — | -| `fix(bulma-ui): …` | patch | — | — | — | -| `perf/refactor/style(bulma-ui): …` | patch | — | — | — | -| `feat(create-bestax): …` | — | minor | — | — | -| `fix(bestax-migrate): …` | — | — | patch | — | -| `feat(bestax-mcp): …` | — | — | — | minor | -| `feat(bulma-ui): …` + `BREAKING CHANGE:` footer | major | — | — | — | -| `docs: …`, `chore: …`, `ci: …`, `test: …`, `build: …` (any scope) | — | — | — | — | +| Commit | bestax-bulma | create-bestax | bestax-migrate | bestax-mcp | eslint-plugin | +| ----------------------------------------------------------------- | ------------ | ------------- | -------------- | ---------- | ------------- | +| `feat(bulma-ui): …` | minor | — | — | — | — | +| `fix(bulma-ui): …` | patch | — | — | — | — | +| `perf/refactor/style(bulma-ui): …` | patch | — | — | — | — | +| `feat(create-bestax): …` | — | minor | — | — | — | +| `fix(bestax-migrate): …` | — | — | patch | — | — | +| `feat(bestax-mcp): …` | — | — | — | minor | — | +| `feat(eslint-plugin): …` | — | — | — | — | minor | +| `feat(bulma-ui): …` + `BREAKING CHANGE:` footer | major | — | — | — | — | +| `docs: …`, `chore: …`, `ci: …`, `test: …`, `build: …` (any scope) | — | — | — | — | — | Notes: - **Breaking changes require a `BREAKING CHANGE:` footer** in the commit body. The angular commit-analyzer preset does **not** parse `feat(bulma-ui)!:` bang headers. - Commits of a scope-gated type (`feat`, `fix`, `perf`, `refactor`, `style`, `revert`) **must** - carry a scope of `bulma-ui`, `docs`, `create-bestax`, `bestax-migrate`, or `bestax-mcp` — + carry a scope of `bulma-ui`, `docs`, `create-bestax`, `bestax-migrate`, `bestax-mcp`, or + `eslint-plugin` — enforced by commitlint ([`commitlint.config.js`](./commitlint.config.js)) via the husky `commit-msg` hook. This is what guarantees the per-scope release rules can't be bypassed by an unscoped commit. @@ -57,6 +61,8 @@ Each package tags and logs its own releases: - `create-bestax@X.Y.Z` tags, changelog at `create-bestax/CHANGELOG.md` - `bestax-migrate@X.Y.Z` tags, changelog at `bestax-migrate/CHANGELOG.md` - `bestax-mcp@X.Y.Z` tags, changelog at `bestax-mcp/CHANGELOG.md` +- `@allxsmith/eslint-plugin-bestax@X.Y.Z` tags, changelog at + `eslint-plugin/CHANGELOG.md` ## Release Process diff --git a/bestax-mcp/data/components/Hero.json b/bestax-mcp/data/components/Hero.json index 01f3d2f5c..a8ad70eb8 100644 --- a/bestax-mcp/data/components/Hero.json +++ b/bestax-mcp/data/components/Hero.json @@ -64,7 +64,7 @@ "name": "color", "type": "Bulma color | 'inherit' | 'current'", "default": null, - "description": "Bulma color modifier for the hero section (renders `is-`). Only `primary`, `link`, `info`, `success`, `warning`, `danger`, `black`, `white`, `light`, and `dark` have shipped CSS for `.hero`. The other accepted values (`black-bis`, `black-ter`, `grey-darker`, `grey-dark`, `grey`, `grey-light`, `grey-lighter`, `inherit`, `current`) emit a class no CSS rule matches, so the hero renders unstyled; they log a console warning in development and will be removed from this union in the next major version.", + "description": "Bulma color modifier for the hero section (renders `is-`). Only `primary`, `link`, `info`, `success`, `warning`, `danger`, `black`, `white`, `light`, and `dark` have shipped CSS for `.hero`. Every other value the union accepts emits a class no CSS rule matches, so the hero renders unstyled; those log a console warning in development and will be removed from this union in the next major version.", "required": false, "inherited": false, "deprecated": false, diff --git a/bestax-mcp/data/components/Notification.json b/bestax-mcp/data/components/Notification.json index c2eb5c175..6cec944c7 100644 --- a/bestax-mcp/data/components/Notification.json +++ b/bestax-mcp/data/components/Notification.json @@ -60,7 +60,7 @@ "name": "color", "type": "Bulma color", "default": null, - "description": "Bulma color modifier for the notification (renders `is-`). Only `primary`, `link`, `info`, `success`, `warning`, `danger`, `black`, `white`, `light`, and `dark` have shipped CSS for `.notification`. The other accepted values (`black-bis`, `black-ter`, `grey-darker`, `grey-dark`, `grey`, `grey-light`, `grey-lighter`) emit a class no CSS rule matches, so the notification renders unstyled; they log a console warning in development and will be removed from this union in the next major version.", + "description": "Bulma color modifier for the notification (renders `is-`). Only `primary`, `link`, `info`, `success`, `warning`, `danger`, `black`, `white`, `light`, and `dark` have shipped CSS for `.notification`. Every other value the union accepts emits a class no CSS rule matches, so the notification renders unstyled; those log a console warning in development and will be removed from this union in the next major version.", "required": false, "inherited": false, "deprecated": false, diff --git a/bestax-mcp/data/components/Progress.json b/bestax-mcp/data/components/Progress.json index 1a64d9c99..01dbbbe88 100644 --- a/bestax-mcp/data/components/Progress.json +++ b/bestax-mcp/data/components/Progress.json @@ -60,7 +60,7 @@ "name": "color", "type": "Bulma color", "default": null, - "description": "Bulma color modifier for the progress bar (renders `is-`). Only `primary`, `link`, `info`, `success`, `warning`, `danger`, `black`, `white`, `light`, and `dark` have shipped CSS for `.progress`. The other accepted values (`black-bis`, `black-ter`, `grey-darker`, `grey-dark`, `grey`, `grey-light`, `grey-lighter`) emit a class no CSS rule matches, so the bar renders unstyled; they log a console warning in development and will be removed from this union in the next major version.", + "description": "Bulma color modifier for the progress bar (renders `is-`). Only `primary`, `link`, `info`, `success`, `warning`, `danger`, `black`, `white`, `light`, and `dark` have shipped CSS for `.progress`. Every other value the union accepts emits a class no CSS rule matches, so the bar renders unstyled; those log a console warning in development and will be removed from this union in the next major version.", "required": false, "inherited": false, "deprecated": false, diff --git a/bestax-mcp/data/components/Valid value constants.json b/bestax-mcp/data/components/Valid value constants.json index 50a1ca975..15c5bac59 100644 --- a/bestax-mcp/data/components/Valid value constants.json +++ b/bestax-mcp/data/components/Valid value constants.json @@ -10,7 +10,7 @@ "storybook": null, "summary": "The `valid*` tuples enumerate the core accepted values for the shared Bulma helper props — public API for building prop types and validation.", "import": "import { Valid value constants } from '@allxsmith/bestax-bulma';", - "doc": "# Valid value constants\n\n## Overview\n\nThe `valid*` tuples enumerate the core accepted values for the shared Bulma helper props — public API for building prop types and validation. Each is a readonly `as const` tuple, so `(typeof validColors)[number]` gives you the exact string-literal union. `useBulmaClasses` (and the per-concern hooks) silently ignore unrecognized values, and every tuple member produces a helper class except `validSchemeColors`, whose values instead produce the `bulmaHelperStyles` inline style (no class). A few props accept documented extras beyond their tuple, noted in the table below: the `display*` props also take `'none'` (rendering the `is-hidden*` visibility class), and the color props also take `'inherit'` and `'current'` — so validate against the prop's TypeScript union, not the tuple alone, when you need the full contract.\n\n---\n\n## Import\n\n```tsx\nimport {\n validColors,\n validSizes,\n validViewports,\n} from '@allxsmith/bestax-bulma';\n```\n\nAll 19 constants are importable the same way.\n\n---\n\n## Constants\n\n| Constant | Values | Used by prop family |\n| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `validColors` | `'primary'`, `'link'`, `'info'`, `'success'`, `'warning'`, `'danger'`, `'black'` … `'white'`, `'light'`, `'dark'` (17 values) | `color`, `backgroundColor` helper props. Component `color` modifier unions are narrower and component-specific; some `validColors` members ship no `is-` CSS (deprecated, warned in dev). See each component's Props table. Scheme-aware components extend their `bgColor` unions with `validSchemeColors` (below). |\n| `validColorShades` | `'00'`, `'05'` … `'95'`, `'invert'`, `'light'`, `'dark'`, `'soft'`, `'bold'`, `'on-scheme'` | `colorShade`, `backgroundColorShade` |\n| `validSchemeColors` | `'scheme-main'`, `'scheme-main-bis'`, `'scheme-main-ter'`, `'scheme-invert'`, `'scheme-invert-bis'`, `'scheme-invert-ter'` | Scheme-aware `bgColor`/`backgroundColor` values. Emit no class — components render them as a dark-mode-safe inline `background-color: var(--bulma-)` style (Bulma ships no `has-background-scheme-*` helpers). |\n| `validSizes` | `'0'`–`'6'`, `'auto'` | Spacing: `m`, `mt`, `mr`, `mb`, `ml`, `mx`, `my`, `p`, `pt`, … `px`, `py` |\n| `validTextSizes` | `'1'`–`'7'` | `textSize` (+ `textSizeMobile` … `textSizeFullhd`) |\n| `validAlignments` | `'centered'`, `'justified'`, `'left'`, `'right'` | `textAlign` (+ viewport variants) |\n| `validTextTransforms` | `'capitalized'`, `'lowercase'`, `'uppercase'`, `'italic'` | `textTransform` |\n| `validTextWeights` | `'light'`, `'normal'`, `'medium'`, `'semibold'`, `'bold'` | `textWeight` |\n| `validFontFamilies` | `'sans-serif'`, `'monospace'`, `'primary'`, `'secondary'`, `'code'` | `fontFamily` |\n| `validDisplays` | `'block'`, `'flex'`, `'inline'`, `'inline-block'`, `'inline-flex'`, `'grid'` — the `display*` props additionally accept `'none'` | `display` (+ `displayMobile` … `displayFullhd`) |\n| `validVisibilities` | `'hidden'`, `'sr-only'`, `'invisible'` | `visibility` (+ viewport variants) |\n| `validFlexDirections` | `'row'`, `'row-reverse'`, `'column'`, `'column-reverse'` | `flexDirection` |\n| `validFlexWraps` | `'nowrap'`, `'wrap'`, `'wrap-reverse'` | `flexWrap` |\n| `validJustifyContents` | `'flex-start'`, `'flex-end'`, `'center'`, `'space-between'`, `'space-around'`, `'space-evenly'`, `'start'`, `'end'`, `'left'`, `'right'` | `justifyContent` |\n| `validAlignContents` | `'flex-start'`, `'flex-end'`, `'center'`, `'space-between'`, `'space-around'`, `'space-evenly'`, `'stretch'` | `alignContent` |\n| `validAlignItems` | `'stretch'`, `'flex-start'`, `'flex-end'`, `'center'`, `'baseline'`, `'start'`, `'end'` | `alignItems` |\n| `validAlignSelfs` | `'auto'`, `'flex-start'`, `'flex-end'`, `'center'`, `'baseline'`, `'stretch'` | `alignSelf` |\n| `validFlexGrowShrink` | `'0'`–`'5'` | `flexGrow`, `flexShrink` |\n| `validViewports` | `'mobile'`, `'tablet'`, `'tablet-only'`, `'touch'`, `'desktop'`, `'desktop-only'`, `'widescreen'`, `'widescreen-only'`, `'fullhd'` | `viewport` (responsive modifier) |\n\n---\n\n## Typing with `(typeof …)[number]`\n\nBecause the arrays are `as const`, indexing them with `number` yields the union of their literal values — the same idiom the library uses internally for its prop types:\n\n```ts\nimport { validColors, validSizes } from '@allxsmith/bestax-bulma';\n\ntype Color = (typeof validColors)[number]; // 'primary' | 'link' | … | 'dark'\ntype Spacing = (typeof validSizes)[number]; // '0' | '1' | … | '6' | 'auto'\n\ninterface MyComponentProps {\n color?: Color;\n m?: Spacing;\n}\n```\n\nThey also work as runtime validators:\n\n```ts\nfunction isColor(value: string): value is (typeof validColors)[number] {\n return (validColors as readonly string[]).includes(value);\n}\n```\n\n:::warning `validSizes` is for spacing, not element sizes\n\n`validSizes` (`'0'`–`'6'` | `'auto'`) enumerates the **spacing helper** scale — the values for `m`/`p` props like `m=\"4\"` (→ `m-4`). Element `size` props are a different axis: `Button`, `Icon`, and friends declare an inline union such as `'small' | 'medium' | 'large'` (Button adds `'normal'`), **not** `validSizes`. Don't use `validSizes` to type a component's `size` prop.\n\n:::\n\n---\n\n## See Also\n\n- [`useBulmaClasses`](./usebulmaclasses.md): The hook that consumes these values and generates the helper classes.\n- [Helper guides](../../guides/helpers/color.md): Task-oriented walkthroughs of the helper-prop system (color, spacing, typography, flex, visibility).", + "doc": "# Valid value constants\n\n## Overview\n\nThe `valid*` tuples enumerate the core accepted values for the shared Bulma helper props — public API for building prop types and validation. Each is a readonly `as const` tuple, so `(typeof validColors)[number]` gives you the exact string-literal union. `useBulmaClasses` (and the per-concern hooks) silently ignore unrecognized values, and every tuple member produces a helper class except `validSchemeColors`, whose values instead produce the `bulmaHelperStyles` inline style (no class). A few props accept documented extras beyond their tuple, noted in the table below: the `display*` props also take `'none'` (rendering the `is-hidden*` visibility class), and the color props also take `'inherit'` and `'current'` — so validate against the prop's TypeScript union, not the tuple alone, when you need the full contract.\n\n---\n\n## Import\n\n```tsx\nimport {\n validColors,\n validSizes,\n validViewports,\n} from '@allxsmith/bestax-bulma';\n```\n\nEvery constant on this page is importable the same way.\n\n### Without loading the library\n\nTooling that only needs the tuples can take them from the `./constants`\nsubpath, which serves the value module on its own, with no React and no\ncomponent code:\n\n```ts\nimport { validColors } from '@allxsmith/bestax-bulma/constants';\n```\n\nIt resolves from both ESM and CommonJS. This is how\n[`@allxsmith/eslint-plugin-bestax`](../../guides/getting-started/eslint-plugin.md)\nvalidates helper values at lint time rather than keeping a copy of them.\n\n---\n\n## Constants\n\n| Constant | Values | Used by prop family |\n| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `validColors` | `'primary'`, `'link'`, `'info'`, `'success'`, `'warning'`, `'danger'`, the greys, and `'black'`, `'white'`, `'light'`, `'dark'`, each of `black`/`white` also in a `-bis` and `-ter` shade | `color`, `backgroundColor` helper props. Component `color` modifier unions are narrower and component-specific; some `validColors` members ship no `is-` CSS (deprecated, warned in dev). See each component's Props table. Scheme-aware components extend their `bgColor` unions with `validSchemeColors` (below). |\n| `validColorShades` | `'00'`, `'05'` … `'95'`, `'invert'`, `'light'`, `'dark'`, `'soft'`, `'bold'`, `'on-scheme'` | `colorShade`, `backgroundColorShade` |\n| `validSchemeColors` | `'scheme-main'`, `'scheme-main-bis'`, `'scheme-main-ter'`, `'scheme-invert'`, `'scheme-invert-bis'`, `'scheme-invert-ter'` | Scheme-aware `bgColor`/`backgroundColor` values. Emit no class — components render them as a dark-mode-safe inline `background-color: var(--bulma-)` style (Bulma ships no `has-background-scheme-*` helpers). |\n| `validSizes` | `'0'`–`'6'`, `'auto'` | Spacing: `m`, `mt`, `mr`, `mb`, `ml`, `mx`, `my`, `p`, `pt`, … `px`, `py` |\n| `validTextSizes` | `'1'`–`'7'` | `textSize` (+ `textSizeMobile` … `textSizeFullhd`) |\n| `validAlignments` | `'centered'`, `'justified'`, `'left'`, `'right'` | `textAlign` (+ viewport variants) |\n| `validTextTransforms` | `'capitalized'`, `'lowercase'`, `'uppercase'`, `'italic'` | `textTransform` |\n| `validTextWeights` | `'light'`, `'normal'`, `'medium'`, `'semibold'`, `'bold'` | `textWeight` |\n| `validFontFamilies` | `'sans-serif'`, `'monospace'`, `'primary'`, `'secondary'`, `'code'` | `fontFamily` |\n| `validDisplays` | `'block'`, `'flex'`, `'inline'`, `'inline-block'`, `'inline-flex'`, `'grid'` — the `display*` props additionally accept `'none'` | `display` (+ `displayMobile` … `displayFullhd`) |\n| `validVisibilities` | `'hidden'`, `'sr-only'`, `'invisible'` | `visibility` (+ viewport variants) |\n| `validFlexDirections` | `'row'`, `'row-reverse'`, `'column'`, `'column-reverse'` | `flexDirection` |\n| `validFlexWraps` | `'nowrap'`, `'wrap'`, `'wrap-reverse'` | `flexWrap` |\n| `validJustifyContents` | `'flex-start'`, `'flex-end'`, `'center'`, `'space-between'`, `'space-around'`, `'space-evenly'`, `'start'`, `'end'`, `'left'`, `'right'` | `justifyContent` |\n| `validAlignContents` | `'flex-start'`, `'flex-end'`, `'center'`, `'space-between'`, `'space-around'`, `'space-evenly'`, `'stretch'` | `alignContent` |\n| `validAlignItems` | `'stretch'`, `'flex-start'`, `'flex-end'`, `'center'`, `'baseline'`, `'start'`, `'end'` | `alignItems` |\n| `validAlignSelfs` | `'auto'`, `'flex-start'`, `'flex-end'`, `'center'`, `'baseline'`, `'stretch'` | `alignSelf` |\n| `validFlexGrowShrink` | `'0'`–`'5'` | `flexGrow`, `flexShrink` |\n| `validViewports` | `'mobile'`, `'tablet'`, `'tablet-only'`, `'touch'`, `'desktop'`, `'desktop-only'`, `'widescreen'`, `'widescreen-only'`, `'fullhd'` | `viewport` (responsive modifier) |\n| `validFloats` | `'left'`, `'right'` | `float` |\n| `validOverflows` | `'clipped'` | `overflow` |\n| `validInteractions` | `'unselectable'`, `'clickable'` | `interaction` |\n| `validCursors` | `'pointer'`, `'help'` | `cursor`. These share no class stem: `pointer` renders `is-clickable` and `help` renders `is-cursor-help`. |\n| `validRadii` | `'radiusless'` | `radius`. The prop removes the border radius; there is no value that adds one. |\n| `validShadows` | `'shadowless'` | `shadow`. Same shape as `radius`: it removes the shadow, so `` renders nothing. |\n| `validResponsives` | `'mobile'`, `'narrow'` | `responsive`. A different axis from `validViewports`: these are the Bulma column and table modifiers. |\n\n---\n\n## Typing with `(typeof …)[number]`\n\nBecause the arrays are `as const`, indexing them with `number` yields the union of their literal values — the same idiom the library uses internally for its prop types:\n\n```ts\nimport { validColors, validSizes } from '@allxsmith/bestax-bulma';\n\ntype Color = (typeof validColors)[number]; // 'primary' | 'link' | … | 'dark'\ntype Spacing = (typeof validSizes)[number]; // '0' | '1' | … | '6' | 'auto'\n\ninterface MyComponentProps {\n color?: Color;\n m?: Spacing;\n}\n```\n\nThey also work as runtime validators:\n\n```ts\nfunction isColor(value: string): value is (typeof validColors)[number] {\n return (validColors as readonly string[]).includes(value);\n}\n```\n\n:::warning `validSizes` is for spacing, not element sizes\n\n`validSizes` (`'0'`–`'6'` | `'auto'`) enumerates the **spacing helper** scale — the values for `m`/`p` props like `m=\"4\"` (→ `m-4`). Element `size` props are a different axis: `Button`, `Icon`, and friends declare an inline union such as `'small' | 'medium' | 'large'` (Button adds `'normal'`), **not** `validSizes`. Don't use `validSizes` to type a component's `size` prop.\n\n:::\n\n---\n\n## See Also\n\n- [`useBulmaClasses`](./usebulmaclasses.md): The hook that consumes these values and generates the helper classes.\n- [Helper guides](../../guides/helpers/color.md): Task-oriented walkthroughs of the helper-prop system (color, spacing, typography, flex, visibility).", "parts": [], "cssVars": [] } diff --git a/bestax-mcp/data/components/useBulmaClasses.json b/bestax-mcp/data/components/useBulmaClasses.json index ef510dafb..fd1564b52 100644 --- a/bestax-mcp/data/components/useBulmaClasses.json +++ b/bestax-mcp/data/components/useBulmaClasses.json @@ -183,7 +183,7 @@ "storybook": null, "summary": "`useBulmaClasses` is a custom React hook that generates Bulma helper class strings from a set of props.", "import": "import { useBulmaClasses } from '@allxsmith/bestax-bulma';", - "doc": "# useBulmaClasses\n\n## Overview\n\n`useBulmaClasses` is a custom React hook that generates Bulma helper class strings from a set of props. It makes it easy to apply color, spacing, alignment, typography, flexbox, and other Bulma utility classes to your components in a type-safe, composable way. It also separates out any non-helper props for direct use on elements.\n\n---\n\n## Import\n\n```tsx\nimport { useBulmaClasses } from '@allxsmith/bestax-bulma';\n```\n\n---\n\n## Usage\n\n:::info Container components pad themselves\nContainer components like `Box`, `Notification`, `Message`, and `Card` ship with Bulma's standard, pleasant padding and stack with sensible default spacing. The examples below only add margin or padding props when spacing itself is what's being demonstrated.\n:::\n\n### Basic Example\n\n```tsx live\nfunction example() {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: 'link',\n backgroundColor: 'primary',\n p: '4',\n textAlign: 'centered',\n id: 'card1',\n });\n\n return JSON.stringify({ bulmaHelperClasses, rest });\n // bulmaHelperClasses: 'has-text-link has-background-primary p-4 has-text-centered'\n // rest: { id: 'card1' }\n}\n```\n\nApply to your component:\n\n```tsx live\nfunction example() {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: 'link',\n backgroundColor: 'primary',\n p: '4',\n textAlign: 'centered',\n id: 'card1',\n });\n\n return (\n
\n Card content\n
\n );\n}\n```\n\n---\n\n### Responsive Props\n\n```tsx live\nfunction example() {\n const { bulmaHelperClasses } = useBulmaClasses({\n color: 'link',\n backgroundColor: 'primary',\n m: '2',\n textAlign: 'centered',\n viewport: 'mobile',\n });\n return JSON.stringify({ bulmaHelperClasses });\n // bulmaHelperClasses: 'has-text-link-mobile m-2-mobile has-text-centered-mobile'\n}\n```\n\n---\n\n### Touch and `-only` Viewports\n\nBeyond the five breakpoint props, `display`/`visibility` also take `touch` (mobile + tablet combined — the breakpoint most often used to hide desktop-only chrome) and the `-only` variants (`tablet-only`, `desktop-only`, `widescreen-only`), each isolating a single breakpoint band:\n\n```tsx live\nfunction example() {\n const { bulmaHelperClasses } = useBulmaClasses({\n displayTouch: 'none',\n displayDesktopOnly: 'flex',\n });\n return JSON.stringify({ bulmaHelperClasses });\n // bulmaHelperClasses: 'is-hidden-touch is-flex-desktop-only'\n}\n```\n\n```tsx live\n\n \n \n \n\n```\n\nThe legacy `viewport` prop widens to all nine values too, so `useBulmaClasses({ display: 'flex', viewport: 'touch' })` produces `is-flex-touch`. One exception: Bulma ships no `is-size-*` classes for the three `-only` bands, so `textSize` combined with a `tablet-only`/`desktop-only`/`widescreen-only` `viewport` degrades to the base `is-size-N` (rather than emitting a dead `is-size-N-tablet-only`); `touch` and the five breakpoints are unaffected.\n\n---\n\n### Grid Display\n\n`display` also accepts `'grid'`, emitting `is-grid`:\n\n```tsx live\n\n \n Cell 1\n \n \n Cell 2\n \n\n```\n\n---\n\n### With Flexbox Helpers\n\nFlexbox properties are divided into two categories:\n\n#### Container Properties\n\nThese properties apply to elements with `display: flex` or `display: inline-flex`:\n\n- `flexDirection` - Direction of flex items\n- `flexWrap` - Whether items wrap\n- `justifyContent` - Alignment along main axis\n- `alignContent` - Alignment of wrapped lines\n- `alignItems` - Alignment along cross axis\n\n#### Item Properties\n\nThese properties apply to flex items (children of flex containers) and **do not require** the element itself to have `display: flex`:\n\n- `alignSelf` - Override container's alignItems for this item\n- `flexGrow` - How much the item should grow\n- `flexShrink` - How much the item should shrink\n\n```tsx live\nfunction example() {\n const { bulmaHelperClasses } = useBulmaClasses({\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n gap: '4',\n });\n return JSON.stringify({ bulmaHelperClasses });\n // bulmaHelperClasses: 'is-flex is-flex-direction-column is-align-items-center is-justify-content-center'\n}\n```\n\nExample with flex items:\n\n```tsx\n// Container with display flex\n\n {/* Items don't need display flex to use alignSelf, flexGrow, flexShrink */}\n \n \n \n\n```\n\n---\n\n### Using with Components\n\n:::note These are illustrations, not replacements\nThe snippets below rebuild simplified components by hand purely to show how `useBulmaClasses` fits inside one. In a real app, use the library's own [Columns / Column](../columns/columns.md) and [Card](../components/card.md) components — they already wire up `useBulmaClasses` (plus prefixes, modifiers, and more) for you.\n:::\n\n#### Columns\n\n`MyColumns` forwards all of its props to `useBulmaClasses` — the `textAlign` and `textTransform` helper props below flow through `props` into the hook (producing `has-text-centered is-uppercase`), while anything the hook doesn't recognize comes back in `rest` and lands on the `
`:\n\n```tsx live\nfunction example() {\n function MyColumns({ children, ...props }) {\n const { bulmaHelperClasses, rest } = useBulmaClasses(props);\n return (\n
\n {children}\n
\n );\n }\n\n return (\n \n
\n Left\n
\n
\n Right\n
\n
\n );\n}\n```\n\n#### Card\n\n```tsx live\nfunction example() {\n const props = {};\n\n const textColor = 'white';\n const bgColor = 'primary';\n const hasShadow = true;\n const className = 'custom-card';\n const children =

Making a custom card

;\n const p = '4';\n\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n p,\n ...props,\n });\n const cardClasses = classNames('card', className, bulmaHelperClasses, {\n 'is-shadowless': !hasShadow,\n });\n return (\n
\n {children}\n
\n );\n}\n```\n\n---\n\n### Colors\n\nUse the `color` prop to apply Bulma color classes. Here, each button demonstrates a different color:\n\n```tsx live\n\n \n \n \n \n \n \n \n \n\n```\n\n### Background Color\n\nUse the `backgroundColor` prop to set the background color. Pair it with a text color that keeps the label readable on each background:\n\n```tsx live\n\n \n \n Primary\n \n \n \n \n Link\n \n \n \n \n Info\n \n \n \n \n Success\n \n \n \n \n Warning\n \n \n \n \n Danger\n \n \n \n \n Black\n \n \n \n \n White\n \n \n\n```\n\n### Scheme Backgrounds and `bulmaHelperStyles`\n\nThe hook returns a third member, `bulmaHelperStyles`, alongside `bulmaHelperClasses` and `rest`. It is `undefined` for every input except a scheme `backgroundColor` (`scheme-main`, `scheme-main-bis`, `scheme-main-ter`, `scheme-invert`, `scheme-invert-bis`, `scheme-invert-ter`): Bulma ships no `has-background-scheme-*` classes, so those values emit no class and instead surface here as a dark-mode-safe inline style tracking Bulma's scheme CSS variables.\n\n```tsx live\nfunction example() {\n const { bulmaHelperClasses, bulmaHelperStyles, rest } = useBulmaClasses({\n color: 'primary',\n backgroundColor: 'scheme-main-bis',\n id: 'band1',\n });\n\n return JSON.stringify({ bulmaHelperClasses, bulmaHelperStyles, rest });\n // bulmaHelperClasses: 'has-text-primary'\n // bulmaHelperStyles: { backgroundColor: 'var(--bulma-scheme-main-bis)' }\n // rest: { id: 'band1' }\n}\n```\n\nPut it on your element with `mergeBulmaStyles`, which merges the helper style with a user-supplied `style` prop (the user style wins, and the result is `undefined` when both are absent so the DOM stays untouched):\n\n```tsx live\nfunction example() {\n const { bulmaHelperClasses, bulmaHelperStyles, rest } = useBulmaClasses({\n backgroundColor: 'scheme-main-bis',\n p: '4',\n });\n\n return (\n \n A dark-mode-safe surface with zero custom CSS\n
\n );\n}\n```\n\nIn a real component, destructure `style` out of your own props and pass it as `mergeBulmaStyles`'s second argument (as `Section` does) — if `style` stays in the object you spread, whichever comes later in JSX silently replaces the other instead of merging.\n\n### Color Shade\n\n`colorShade` refines the **text color** — it pairs with the hook's `color` prop (exposed as `textColor` on components, where `color` is the component's own modifier). Here, each line steps through the primary palette:\n\n```tsx live\n<>\n \n Primary 10\n \n \n Primary 30\n \n \n Primary 60\n \n \n Primary 90\n \n \n Primary Invert\n \n\n```\n\n### Background Color Shade\n\nBackground shades use their own prop: `backgroundColorShade` pairs with `backgroundColor`, independently of `colorShade` (which pairs with the text color). Low shade numbers are dark and high numbers are light, so dark shades get light text and light shades get dark text to keep each label readable:\n\n```tsx live\n\n \n \n Primary 10\n \n \n \n \n Primary 30\n \n \n \n \n Primary 60\n \n \n \n \n Primary 90\n \n \n \n \n Primary Invert\n
\n \n\n```\n\nBecause the two shade props are independent, you can shade the text and the background at the same time — a dark background shade with a light text shade of the same color reads well in both themes:\n\n```tsx live\n\n Primary text shade 85 on a primary background shade 15\n\n```\n\n### Margin\n\nUse margin props to add spacing. Here, margin is applied to buttons, and to a row of buttons using the `Buttons` component:\n\n```tsx live\n<>\n \n \n \n \n \n \n \n \n \n\n```\n\n### Text Size\n\nApply `textSize` to Notifications to illustrate different sizes:\n\n```tsx live\n<>\n Size 1\n Size 3\n Size 5\n Size 7\n\n```\n\n### Text Align\n\nApply `textAlign` to a Box to illustrate text alignment:\n\n```tsx live\n<>\n Centered text\n Right aligned text\n Left aligned text\n\n```\n\n### Text Transform\n\nApply `textTransform` to a Content tag with a paragraph of the Declaration of Independence:\n\n```tsx live\n<>\n \n \n

\n We hold these truths to be self-evident, that all men are created\n equal...\n

\n
\n
\n \n \n

\n We hold these truths to be self-evident, that all men are created\n equal...\n

\n
\n
\n \n \n

\n We hold these truths to be self-evident, that all men are created\n equal...\n

\n
\n
\n \n \n

\n We hold these truths to be self-evident, that all men are created\n equal...\n

\n
\n
\n\n```\n\n### Text Weight\n\nApply `textWeight` to Content tags to illustrate different font weights:\n\n```tsx live\n<>\n \n \n

\n We hold these truths to be self-evident, that all men are created\n equal...\n

\n
\n
\n \n \n

\n We hold these truths to be self-evident, that all men are created\n equal...\n

\n
\n
\n \n \n

\n We hold these truths to be self-evident, that all men are created\n equal...\n

\n
\n
\n \n \n

\n We hold these truths to be self-evident, that all men are created\n equal...\n

\n
\n
\n\n```\n\n### Font Family\n\nUse Content blocks with different font families:\n\n```tsx live\n<>\n \n \n

\n We hold these truths to be self-evident, that all men are created\n equal...\n

\n
\n
\n \n \n

\n We hold these truths to be self-evident, that all men are created\n equal...\n

\n
\n
\n \n \n

\n We hold these truths to be self-evident, that all men are created\n equal...\n

\n
\n
\n \n \n

\n We hold these truths to be self-evident, that all men are created\n equal...\n

\n
\n
\n \n \n

\n We hold these truths to be self-evident, that all men are created\n equal...\n

\n
\n
\n\n```\n\n### Visibility\n\nShow three buttons, one hidden, one screen-reader only:\n\n```tsx live\n\n \n \n \n\n```\n\n### Overflow\n\nShow a Box with clipped overflow:\n\n```tsx live\n\n This is a very long line of text that will be clipped and not overflow the\n box.\n\n```\n\n### Overlay\n\nShow an overlay (toggle with button click):\n\n```tsx\nfunction OverlayExample() {\n const [show, setShow] = useState(false);\n return (\n \n \n \n );\n}\n```\n\n### Interaction\n\nShow text in a Box with `unselectable`, and another Box with `clickable`:\n\n```tsx live\n<>\n This text cannot be selected.\n This box is clickable.\n\n```\n\n### Cursor\n\nUse the `cursor` prop to set the cursor style. `'pointer'` reuses Bulma's `is-clickable` class, while `'help'` uses the project-level `is-cursor-help` class:\n\n```tsx live\n<>\n Cursor: pointer (uses is-clickable)\n Cursor: help\n\n```\n\n### Radius\n\nShow buttons with `radiusless`:\n\n```tsx live\n\n \n \n\n```\n\n### Shadowless\n\nShow a Box with `shadowless`:\n\n```tsx live\nThis box has no shadow.\n```\n\n### ClassName\n\nShow a Message with a custom className:\n\n```tsx live\n\n This message uses a custom className.\n\n```\n\n### Clearfix\n\nShow containers with and without clearfix to demonstrate the importance of clearing floated elements:\n\n```tsx live\n<>\n \n Without clearfix (container collapses)\n \n \n \n \n \n\n \n With clearfix (container wraps its floats)\n \n \n \n \n \n\n```\n\n### Relative Position\n\nShow a container with relative positioning that provides context for absolutely positioned children:\n\nBulma only ships an `is-relative` helper — absolute positioning itself has no helper class, so the child keeps a minimal `position/top/right` inline style while everything visual comes from props:\n\n```tsx live\n\n This container has position: relative\n \n Absolutely positioned child\n \n\n```\n\n---\n\n### Skeleton Examples\n\nThe `skeleton` prop applies Bulma's skeleton loading effect. Here are examples for each component:\n\n#### Skeleton Button\n\n```tsx live\n\n```\n\n#### Skeleton Buttons Group\n\n```tsx live\n\n \n \n \n\n```\n\n#### Skeleton Icon\n\n```tsx live\n\n```\n\n#### Skeleton Image\n\n```tsx live\n\"Skeleton\n```\n\n#### Skeleton Notification\n\n```tsx live\nSkeleton notification message.\n```\n\n#### Skeleton Tag\n\n```tsx live\nSkeleton Tag\n```\n\n#### Skeleton Title\n\n```tsx live\n\n Skeleton Title\n\n```\n\n#### Skeleton SubTitle\n\n```tsx live\n\n Skeleton SubTitle\n\n```\n\n#### Skeleton Input\n\n```tsx live\n\n```\n\n#### Skeleton TextArea\n\n```tsx live\n