create-bestax 4.2.19 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +13 -3
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +1 -0
  4. package/dist/constants.d.ts +12 -0
  5. package/dist/constants.d.ts.map +1 -1
  6. package/dist/constants.js +65 -0
  7. package/dist/project-creator.d.ts +8 -0
  8. package/dist/project-creator.d.ts.map +1 -1
  9. package/dist/project-creator.js +58 -2
  10. package/dist/prompts.d.ts +1 -0
  11. package/dist/prompts.d.ts.map +1 -1
  12. package/dist/prompts.js +15 -1
  13. package/package.json +3 -3
  14. package/templates/skills/bestax-custom-component/references/component-catalog.md +3 -1
  15. package/templates/skills/bestax-icons/references/icon-libraries.md +14 -1
  16. package/templates/skills/bestax-layout-scaffold/SKILL.md +15 -0
  17. package/templates/skills/bestax-migrate/SKILL.md +10 -4
  18. package/templates/skills/bestax-migrate/references/bloomer/prop-map.md +1 -1
  19. package/templates/skills/bestax-migrate/references/bloomer/unmappables.md +4 -1
  20. package/templates/skills/bestax-migrate/references/bulma-classes/component-map.md +49 -15
  21. package/templates/skills/bestax-migrate/references/bulma-classes/prop-map.md +4 -2
  22. package/templates/skills/bestax-migrate/references/bulma-classes/unmappables.md +166 -30
  23. package/templates/skills/bestax-migrate/references/rbx/prop-map.md +1 -1
  24. package/templates/skills/bestax-migrate/references/react-bulma-components/unmappables.md +2 -2
  25. package/templates/skills/bestax-optimize/references/modular-build.md +3 -1
  26. package/templates/skills/bestax-theming/SKILL.md +13 -10
  27. package/templates/skills/bestax-theming/examples/dark-mode.tsx +7 -6
  28. package/templates/skills/bestax-theming/examples/theme-config.tsx +10 -3
  29. package/templates/skills/bestax-theming/references/css-variables.md +89 -11
  30. package/templates/skills/bestax-theming/references/themeable-components.md +2 -0
  31. package/templates/vite/README.md +2 -2
  32. package/templates/vite/_oxlintrc.json +8 -0
  33. package/templates/vite/package.json +8 -12
  34. package/templates/vite/vite.config.js +1 -1
  35. package/templates/vite-ts/README.md +2 -2
  36. package/templates/vite-ts/_oxlintrc.json +8 -0
  37. package/templates/vite-ts/package.json +10 -15
  38. package/templates/vite/eslint.config.js +0 -29
  39. package/templates/vite-ts/eslint.config.js +0 -26
@@ -6,9 +6,13 @@ wrapper library. The codemod converts an element only when the bestax component
6
6
  stays as written, with a `TODO(bestax-migrate)` when there is something to decide (see
7
7
  [unmappables.md](unmappables.md)). The exceptions are deliberate. Font Awesome's own
8
8
  `<i aria-hidden="true">` alone in a `.icon` that carries nothing assistive tech reads becomes
9
- `Icon`, which puts that attribute on the `.icon` instead, since the glyph is hidden either way
10
- (see `.icon` below). And a Bulma 0.9 helper that v1 removed, which styles nothing now, becomes
11
- the v1 helper that does its job (the [prop map](prop-map.md) lists them).
9
+ `Icon`, which puts that attribute on the `.icon` instead, since the glyph is hidden either way.
10
+ Such a `.icon` around a bare glyph, in a link or button its own `aria-label` or
11
+ `aria-labelledby` names, becomes `Icon` too, which hides it, since the label is what assistive
12
+ tech reads for the control. Font Awesome 6's long name for a style (`fa-brands`) becomes the
13
+ `bestax-icons` skill's `variant` (`brands`), which renders the short name (`fab`), the same
14
+ style (see `.icon` below). And a Bulma 0.9 helper that v1 removed, which styles nothing now,
15
+ becomes the v1 helper that does its job (the [prop map](prop-map.md) lists them).
12
16
 
13
17
  A class the codemod doesn't know, such as the app's own `pricing-card`, rides along in
14
18
  `className`, which every bestax component passes through. So does a Bulma class with no bestax
@@ -156,16 +160,17 @@ renders its own `<a>` with tab roles and puts its label in a `<span>`, so conver
156
160
  themselves is by hand (something that sat beside the text in the `<a>`, a `Tag` say, then wants a
157
161
  `Span display="flex" alignItems="center"` around it and the label). A `.icon` converts when it
158
162
  carries the attribute `Icon` writes otherwise: `aria-hidden` with no name, no `role` and no
159
- `tabIndex`, and `role="img"` beside an `aria-label` or `aria-labelledby`. When all it holds is a
160
- bare, empty `<i>` naming a Font Awesome or Material Design Icons glyph, the `<i>` goes and its
161
- classes become `Icon`'s props, read as `IconText` reads them (below), and the `Icon` closes itself:
163
+ `tabIndex`, and `role="img"` beside an `aria-label` or `aria-labelledby`. Those attributes are
164
+ left out, since `Icon` renders them by itself. When all it holds is a bare, empty `<i>` naming a
165
+ Font Awesome or Material Design Icons glyph, the `<i>` goes and its classes become `Icon`'s
166
+ props, read as `IconText` reads them (below), and the `Icon` closes itself:
162
167
 
163
168
  ```jsx
164
169
  <span className="icon is-small" aria-hidden="true">
165
170
  <i className="fab fa-github fa-lg"></i>
166
171
  </span>
167
- // becomes
168
- <Icon library="fa" name="github" variant="brands" features="fa-lg" size="small" aria-hidden="true" />
172
+ // becomes, rendering aria-hidden="true" on the .icon
173
+ <Icon name="github" variant="brands" features="fa-lg" size="small" />
169
174
  ```
170
175
 
171
176
  Font Awesome's own markup hides the `<i>` instead, and converts the same way when the `.icon`
@@ -178,12 +183,39 @@ place a conversion changes the markup, on purpose:
178
183
  <i className="fas fa-envelope" aria-hidden="true"></i>
179
184
  </span>
180
185
  // becomes, rendering aria-hidden="true" on the .icon
181
- <Icon library="fa" name="envelope" size="small" className="is-left" />
186
+ <Icon name="envelope" size="small" className="is-left" />
187
+ ```
188
+
189
+ A `.icon` with no `aria-hidden` converts the same way when it sits in a `<button>`, or in a link
190
+ (an `<a>` with an `href`), that its own `aria-label` or `aria-labelledby` names. That label is
191
+ what assistive tech reads for the control, in place of what it holds, so the icon is decoration
192
+ there, and `Icon` hides it. That is another place a conversion changes the markup, on purpose,
193
+ held to the same shape: a bare glyph, in a `.icon` that carries nothing but its classes, an `id`,
194
+ a `style` and `data-` attributes.
195
+
196
+ ```jsx
197
+ <a className="button" href="https://github.com/" aria-label="GitHub">
198
+ <span className="icon"><i className="fab fa-github"></i></span>
199
+ </a>
200
+ // becomes, rendering aria-hidden="true" on the .icon
201
+ <Button as="a" href="https://github.com/" aria-label="GitHub">
202
+ <Icon name="github" variant="brands" />
203
+ </Button>
182
204
  ```
183
205
 
184
206
  Around anything else, an `<i>` with an attribute of its own or an `<svg>` say, it converts around
185
- what it holds, kept as written. Font Awesome 4's `fa` reads as `variant="fa"`, which `Icon`
186
- renders as written.
207
+ what it holds, kept as written.
208
+
209
+ The glyph's props are spelled the way the `bestax-icons` skill writes them. `library` is left out
210
+ for Font Awesome, `Icon`'s default, and written for Material Design Icons. The style is `variant`:
211
+ none for `fas`, and `regular`, `brands`, `light`, `duotone` or `thin` for the others. Font
212
+ Awesome 6's long names for them (`fa-solid`, `fa-brands`, ...) read the same way, and `Icon`
213
+ renders the short name (`fab`), the class Font Awesome 5 knows, which 6 and 7 style the same:
214
+ a third place the markup changes on purpose. Beside `fa-sharp`, whose family Font Awesome 7's
215
+ short name would set back to the classic one, the long name stays as written. Font Awesome 4's
216
+ `fa` reads as `variant="fa"`, which `Icon` renders as written: 4 styles nothing else, and some
217
+ 4 names draw another glyph in 5 and later (the `bestax-icons` skill's
218
+ `references/icon-libraries.md` lists them).
187
219
 
188
220
  A `.menu` converts with its `.menu-label`s and `.menu-list`s, and so do the items in a list. An
189
221
  item has no class to go by, so the codemod finds it by where it sits: a `<li>` whose nearest
@@ -314,14 +346,16 @@ as `items`.
314
346
  <span>Home</span>
315
347
  </span>
316
348
  // becomes
317
- <IconText iconProps={{ library: "fa", name: "home", "aria-hidden": "true" }}>Home</IconText>
349
+ <IconText iconProps={{ name: "home" }}>Home</IconText>
318
350
  ```
319
351
 
320
352
  The glyph is read from the `<i>`: its Font Awesome style class (`fas`, `far`, `fa-solid`, ...) or
321
353
  `mdi`, the one class that names the glyph (`fa-home` as `name: "home"`), and the rest as
322
- `features`. `library` is always written, so a `ConfigProvider` with another `iconLibrary` doesn't
323
- change what renders. The `.icon`'s own classes and attributes join the glyph's props as they
324
- would on `Icon`, under their own names.
354
+ `features`, spelled as on `Icon` (above). Like every conversion, this takes the app's
355
+ `ConfigProvider` as unset: one with another `iconLibrary` needs `library: "fa"` written back on
356
+ the Font Awesome ones, as one with a `classPrefix` changes every component's classes. The
357
+ `.icon`'s own classes and attributes join the glyph's props as they would on `Icon`, under their
358
+ own names, but for those `Icon` renders by itself.
325
359
 
326
360
  It converts only when every `.icon` inside converts to `Icon` on its own and holds a bare, empty
327
361
  `<i>` naming a Font Awesome or Material Design Icons glyph, and the only other children are those
@@ -2,7 +2,8 @@
2
2
 
3
3
  Which class becomes which prop, per component, then the helper classes every component takes.
4
4
  A class that becomes no prop stays in `className`. So does a second class for a prop already
5
- written (`is-small is-large` keeps `is-large`), so the element still renders both.
5
+ written (`is-small is-large` keeps `is-large`), so the element still renders both. A second
6
+ color gets a `modifier:<root>` TODO (`modifier:button`), since only one of the two may show.
6
7
 
7
8
  `{viewport}` is one of `mobile`, `tablet`, `desktop`, `widescreen`, `fullhd` unless a row says
8
9
  otherwise, and the prop takes the matching suffix: `is-6-tablet` → `sizeTablet="6"`.
@@ -74,7 +75,8 @@ bestax picks the heading from `size` (`size="3"` renders an `<h3>`) unless `as="
74
75
  `<h2>`, `is-4` stays a class and the element becomes `<Title as="h2" className="is-4">`, which
75
76
  renders the same `<h2>`. To write it as a prop, `textSize="4"` keeps the `<h2>` and sizes it the
76
77
  same: it renders Bulma's `is-size-4`, which has the font size `is-4` gives a title or subtitle,
77
- and as a Bulma helper it is `!important`, so check a title the page's own CSS sizes.
78
+ and as a Bulma helper it is `!important`, so check a title the page's own CSS sizes. The
79
+ codemod's `modifier:title` (or `modifier:subtitle`) TODO on the element says the same.
78
80
 
79
81
  ### Other components
80
82
 
@@ -104,15 +104,21 @@ and beside a `role` or `tabIndex` `Icon` writes no `aria-hidden` at all. Give a
104
104
  that carries it beside something else,
105
105
  an `id` say, keeps its `.icon` as markup too: the `.icon` holds nothing else, so the attribute
106
106
  hides the same glyph on the `.icon`. Move it up and re-run, and the `.icon` converts around the
107
- `<i>`. An icon in a link or
108
- button that its `aria-label` or its text names is decoration there, so give it
109
- `aria-hidden="true"`. One that is all a link or button shows names nothing a screen reader can
110
- read: give the link or button an `aria-label` that says what it does, then hide the icon. The
107
+ `<i>`. An icon around a bare glyph in a `<button>`, or a link, that its own `aria-label` or
108
+ `aria-labelledby` names converts as it is, another exception: the label is what assistive tech
109
+ reads for the control, so the icon is decoration there, and `Icon` hides it. That takes the same
110
+ `.icon` as the move: its classes, an `id`, a `style` and `data-` attributes, and no
111
+ `aria-hidden`. An icon in a control its text names is decoration too, so give it
112
+ `aria-hidden="true"`, and so is one in a named control it doesn't convert in (a `title` on the
113
+ `.icon`, say). An `<a>` with no `href` is no link and has no role, and ARIA lets nothing name it,
114
+ so its `aria-label` does nothing: give it an `href`, or make it a `<button>`, then re-run. One
115
+ that is all a link or button shows names nothing a screen reader can read: give the link or
116
+ button an `aria-label` that says what it does, then re-run, and the icon converts hidden. The
111
117
  `<svg class="svg-inline--fa">` Font Awesome's script draws in place of an `<i>` converts as
112
118
  `Icon`'s children once its `.icon` is hidden, or write `Icon` with the glyph's `name` in its place,
113
119
  which renders the `<i>` the script draws from. Inside a `.control` with `has-icons-left` or
114
120
  `has-icons-right`, a hidden `.icon` can go instead, as that `Control`'s `iconLeft` or
115
- `iconRight` (`iconLeft={{ library: "fa", name: "envelope", size: "small" }}`), which renders it
121
+ `iconRight` (`iconLeft={{ name: "envelope", size: "small" }}`), which renders it
116
122
  after the `Control`'s children. Each TODO says which of these applies, and what the icon converts
117
123
  to.
118
124
  `Pagination` renders Bulma's own `role="navigation"` and `aria-label="pagination"`, and
@@ -370,13 +376,47 @@ in the browser:
370
376
  `aria-expanded` and keyboard handling. The codemod turned the `.has-dropdown` item around it
371
377
  into a `Navbar.Item` that keeps the class; to get the dropdown behavior, replace that item
372
378
  with `Navbar.Dropdown` (`hoverable` for `is-hoverable`) and the link with `Navbar.Link`.
373
- - **`family:checkbox`**, **`family:radio`**, **`family:checkboxes`**, **`family:radios`**:
374
- bestax renders its own styled checkbox and radio markup, not Bulma's.
379
+ - **`family:checkbox`**: `Checkbox` renders a `<label class="styled-checkbox checkbox">` around
380
+ its `<input>`, hidden, and draws a box of its own beside it. Write it in place of the element
381
+ that carries `.checkbox`, Bulma's `<label>` or a bare `<input type="checkbox">`:
382
+ - The `<input>`'s attributes go on `Checkbox`, which passes `id`, `name`, `aria-label` and the
383
+ rest to its `<input>`. A `<label>`'s text becomes its children.
384
+ - Drive it with `checked` and `onChange`, and add `indeterminate` for a select-all box that
385
+ shows some of its rows checked.
386
+ - Its `className` goes on its `<label>`, which declares the `--bulma-checkbox-*` variables. For
387
+ the page's colors, set those in a rule for your class beside `.styled-checkbox`, so the rule
388
+ wins whatever order the stylesheets load in, and leave `color` unset.
389
+ - Where the page put `.checkbox` on a bare `<input>`, drop its own rules for that box. bestax
390
+ draws its own, and `.checkbox` is on its `<label>` now, so those rules would land there.
391
+
392
+ ```tsx
393
+ <Checkbox
394
+ id="select-all"
395
+ aria-label="Select all guests"
396
+ className="guest-check"
397
+ checked={allChecked}
398
+ indeterminate={someChecked && !allChecked}
399
+ onChange={toggleAll}
400
+ />
401
+ ```
402
+
403
+ ```css
404
+ .styled-checkbox.guest-check {
405
+ --bulma-checkbox-active-color: #e8505b;
406
+ }
407
+ ```
408
+
409
+ - **`family:radio`**, **`family:checkboxes`**, **`family:radios`**: bestax renders its own
410
+ styled radio markup, and its own `.field` and `.control` around a group, not Bulma's.
375
411
  - **`family:modal`**: `Modal` writes a `data-testid` of its own and adds dialog attributes and
376
412
  focus handling, so the `.modal` root stays markup while its background, content and card parts
377
- convert on their own. Rebuild the root with `Modal` around them, and drive it with its open prop.
413
+ convert on their own. Rebuild the root with `Modal` around them, drive it with its open prop,
414
+ and pass `onClose`, which Escape and `Modal.Close` call. The converted `Modal.Background`
415
+ doesn't call it on its own: give it `onClick={onClose}` for the background to close the modal.
378
416
  A `.modal-close` stays too, since `Modal.Close` renders `.delete` unless it's floating: write
379
- `<Modal.Close variant="floating" />` in its place. When the
417
+ `<Modal.Close variant="floating" />` in its place. A card head's `.delete` converts to `Delete`,
418
+ which doesn't call `onClose` either, so write `<Modal.Close />` there instead: it renders the
419
+ same `.delete`. When the
380
420
  page has to look the same, keep `Modal` rather than `Dialog` or `Toast`: those render
381
421
  bestax's own `.dialog` and `.toast` markup, which Bulma's stylesheet doesn't style.
382
422
  - **`family:dropdown`**: `Dropdown` renders its own trigger and menu from props.
@@ -406,16 +446,35 @@ reason), or beside a class for the same prop (`p-2`, before it or after), which
406
446
  since it's what the page renders today. Write `m-0` or `p-0` in its place, or drop it if the
407
447
  spacing it once removed no longer matters.
408
448
 
409
- ## A class that styles nothing where it is: `modifier:<root>`
449
+ ## A modifier class that needs a look: `modifier:<root>`
410
450
 
411
451
  `modifier:image`: an `.image` size or ratio (`is-64x64`, `is-square`, `is-4by3`) on an element
412
452
  without `.image`. Bulma styles them only on the element that carries `.image`, so this one
413
453
  sizes nothing, unless the project's own CSS gives the class a rule. If the element is the
414
454
  image's box, add `image`. On a tag `Image` renders (the component map's `.image` row lists
415
- them), re-run and it can convert; on any other tag it stays markup, and the TODO says so.
416
- Otherwise drop the class. A computed `className` the codemod can't read whole gets no such TODO, since a class it
455
+ them), re-run and it can convert; on any other tag it stays markup, and the TODO says so. Where
456
+ the element converted to something else anyway (a `<figure>` to `Figure`), a re-run finds a
457
+ component, so the TODO says to convert it to `Image` by hand. Otherwise drop the class. A computed `className` the codemod can't read whole gets no such TODO, since a class it
417
458
  can't see may be the `.image`.
418
459
 
460
+ `modifier:button` (and `modifier:hero`, `modifier:tag`, `modifier:notification`,
461
+ `modifier:progress`, `modifier:navbar`): a second color on an element that converted
462
+ (`button is-black is-primary`). The component takes one `color`, the first color class on the
463
+ element, and the other stays in `className`, which renders the same. Two colors on one element
464
+ look the way Bulma's stylesheet settles them, not the order they're written in, so one of
465
+ them may be doing nothing. Look at what the page shows, write the color you want as `color`,
466
+ and drop the other class. A color a `clsx` or `classnames` condition adds gets no TODO, since
467
+ that is a state the page asks for.
468
+
469
+ `modifier:title`, `modifier:subtitle`: a heading size on a heading it doesn't name
470
+ (`<h1 className="title is-3">`). bestax picks the heading from `size` (`size="3"` renders an
471
+ `<h3>`), so the size stays in `className` and the element becomes `<Title className="is-3">`,
472
+ the same `<h1>`. To write it as a prop, as the MCP server's `lookup_bulma_classes` says,
473
+ `textSize="3"` in its place keeps the `<h1>` and sizes it the same. It renders Bulma's
474
+ `is-size-3`, which is `!important`, so check a title the page's own CSS sizes. There is no TODO
475
+ when an `is-size-*` beside it writes `textSize` already, or when the element has two size
476
+ classes.
477
+
419
478
  ## A whole file it left alone
420
479
 
421
480
  These come only when the file has an element the codemod would convert (a computed className
@@ -511,21 +570,30 @@ neither, so load what the app still needs once for the whole app, from its entry
511
570
  with `ERESOLVE`.
512
571
  - 6 and 7: keep it as it is, or install it and import its `css/all.min.css` from the entry.
513
572
  - 5: the CDN link keeps working, since 5 reads the classes `Icon` writes. From npm, take 6 or 7,
514
- which still read 5's names.
573
+ which still read 5's names, many of them since renamed (`search` is `magnifying-glass`). But
574
+ 6 turned the handsets round, so to keep the glyph the page shows, write 5's `phone` and
575
+ `phone-square` as `phone-flip` and `square-phone-flip`, and 5's `phone-alt` and
576
+ `phone-square-alt` as `phone` and `square-phone`.
515
577
  - 4: its classes are `fa fa-<name>`. The codemod keeps that `fa` on each icon it converts
516
578
  (`variant="fa"`), which 4 styles, but an `Icon` given no `variant` writes `fas fa-<name>`,
517
579
  which 4 doesn't, so for the icons written from here on, install 6 or 7 with
518
580
  `css/all.min.css`, then either import `css/v4-shims.min.css` beside it so
519
581
  the page's 4 names keep working, or move them to the current names (`fa-envelope-o` is
520
- `envelope` with `variant="regular"`).
582
+ `envelope` with `variant="regular"`). A brand's glyph under `fa` (`fa fa-github`) needs
583
+ `variant="brands"` then: 5 and later draw brands in a style of their own, and without the
584
+ shims show a missing-glyph box for one under `fa`.
585
+ - A URL that doesn't say its version: the TODO points at the `bestax-icons` skill's
586
+ `references/icon-libraries.md`, which lists the older names that draw another glyph in 6
587
+ and 7.
521
588
  - A kit (`kit.fontawesome.com`): keep loading it from `index.html`, or install Font Awesome from
522
589
  npm instead. A URL with no version: the same, with the v4 shims beside it if the page's names
523
590
  are 4's (`fa fa-<name>`).
524
- - **`html-asset:bulma-extension`**: a `bulma-*` package (or `@creativebulma/bulma-*`). An
525
- extension's stylesheet and script are one TODO, with both lines. Where bestax has a component
526
- for what it does, the TODO names it; render that in place of the extension's markup and script,
527
- whose styles ship in `extras.css` and `bestax.css`, and stop loading the extension once nothing
528
- the app keeps uses its classes:
591
+ - **`html-asset:bulma-extension`**: a `bulma-*` package (or `@creativebulma/bulma-*`). A file of
592
+ one extension inside the `bulma-extensions` package (`bulma-extensions/bulma-carousel/dist/…`)
593
+ counts as that extension's. An extension's stylesheet and script are one TODO, with both lines.
594
+ Where bestax has a component for what it does, the TODO names it; render that in place of the
595
+ extension's markup and script, whose styles ship in `extras.css` and `bestax.css`, and stop
596
+ loading the extension once nothing the app keeps uses its classes:
529
597
 
530
598
  | Extension | bestax |
531
599
  | ------------------------------------------------------- | -------------------------------------------------------------------------------------- |
@@ -545,6 +613,10 @@ neither, so load what the app still needs once for the whole app, from its entry
545
613
  | `bulma-quickview` | `Sidebar` |
546
614
  | `bulma-toast` | `Toast`: `toast.show()` from code, with a `ToastContainer` once |
547
615
 
616
+ bulma-carousel's `is-covered` images are the exception: its stylesheet crops them only inside
617
+ its own slides, which `Carousel` doesn't render, so keeping it wouldn't keep them cropped. The
618
+ TODO says so when the page has them, and `html-extension:is-covered` has the rule to add.
619
+
548
620
  `bulma-extensions`, the bundle, holds several of these: replace the ones in the table, and keep
549
621
  loading the bundle only for the rest. **bulma-social** has no bestax component: keep loading it,
550
622
  since its classes (`is-twitter`, `is-github`) style Bulma's `.button`, which `Button` renders,
@@ -556,6 +628,20 @@ neither, so load what the app still needs once for the whole app, from its entry
556
628
  </Button>
557
629
  ```
558
630
 
631
+ It also ships one stylesheet per brand, `css/single/<brand>/<brand>.min.css`, a few kB each
632
+ against about 100 kB for every brand in `css/all.min.css`. Each holds the brand's button color
633
+ (`is-<brand>`) and its text and background helpers (`has-text-<brand>`,
634
+ `has-background-<brand>`, with `-dark` and `-light`). Every brand has a file from 3.2.0 on
635
+ (`slack`'s came in 2.1.0, `x`'s in 3.1.0 and `twitch`'s in 3.2.0). So with `bulma-social` 3.2.0
636
+ or later installed from npm, import only the brands the app shows, from its entry. The TODO names the files for the brands
637
+ in the page's markup, so an app of several pages imports each brand any of them shows, and one
638
+ a script adds as well:
639
+
640
+ ```ts
641
+ import 'bulma-social/css/single/github/github.min.css';
642
+ import 'bulma-social/css/single/twitter/twitter.min.css';
643
+ ```
644
+
559
645
  Any other extension: keep loading it while the page's markup uses its classes, which go on
560
646
  bestax's components through `className`, and under `--css bestax` or `--css bulma` check it
561
647
  works with Bulma 1, which an extension can predate.
@@ -572,9 +658,17 @@ neither, so load what the app still needs once for the whole app, from its entry
572
658
  - **`html-asset:stylesheet`**: any other stylesheet. The page's own, beside it (in its folder or
573
659
  one below), is imported once for the app in the order the page loads it, so its rules still win
574
660
  over Bulma's where they did (a module beside the page imports `css/styles.css` as
575
- `./css/styles.css`), or its rules move into the app's stylesheet. For one that isn't in the
576
- page's folder, the TODO says only that: the page's `<base href>`, if it has one, may have loaded
577
- it from somewhere else, so find the one it meant, or drop it.
661
+ `./css/styles.css`), or its rules move into the app's stylesheet. A relative URL in it (a
662
+ `url()`, or an `@import` written as a string) resolves against the stylesheet's own folder, not
663
+ the page's, so the TODO names each one it finds: `url('../media/background.webp')` in
664
+ `css/styles.css` points at `media/background.webp` beside the page. Imported where it is, the
665
+ bundler still resolves them. Moved into the app's stylesheet (`src/index.css`), each needs
666
+ rewriting against that file's folder, or copy the files into the app's `public/` folder and
667
+ point at them from the site root (`url('/media/background.webp')`), which the TODO spells out
668
+ for each one inside the page's folder. An `image-set()` that names a file as a bare string
669
+ isn't read, so check those by hand. For a stylesheet that isn't in the page's folder, the TODO
670
+ says only that: the page's `<base href>`, if it has one, may have loaded it from somewhere
671
+ else, so find the one it meant, or drop it.
578
672
  - **`html-asset:script`**: any other script. The component runs none: load a library the app still
579
673
  needs in `index.html` (or install it), and port what the page's own scripts did into the
580
674
  component. A script of the page's own, beside it, is read the way an inline script is, so one
@@ -620,8 +714,9 @@ read for the burger, a carousel and a theme toggle too, and its TODO names the f
620
714
  ```
621
715
 
622
716
  - **`html-script:modal`** opens or closes a modal. `Modal` shows while `active` is true and calls
623
- `onClose` from its background, its close button and Escape, so keep the open state in
624
- `useState` (the `family:modal` TODO covers rebuilding the markup).
717
+ `onClose` on Escape and from a `Modal.Close` with no `onClick` of its own. `Modal.Background`
718
+ doesn't call it on its own, so give it `onClick={onClose}` for the background to close the
719
+ modal. Keep the open state in `useState` (the `family:modal` TODO covers rebuilding the markup).
625
720
  - **`html-script:dismiss`** removes an element when its `.delete` is clicked. `Notification`
626
721
  takes `hasDelete` and `onDelete`, `Tag` takes `onDelete` and `Message` takes `onClose`; render
627
722
  the element only while it's in state.
@@ -643,7 +738,8 @@ read for the burger, a carousel and a theme toggle too, and its TODO names the f
643
738
  window's width in your own code. `Carousel` loops unless `repeat={false}`, while
644
739
  bulma-carousel stops at the ends unless `loop` (back to the start) or `infinite` (round
645
740
  without a break) is set, so a carousel with neither takes `repeat={false}`. Its styles ship in
646
- bestax's `extras.css` and `bestax.css`, not in Bulma's stylesheet.
741
+ bestax's `extras.css` and `bestax.css`, not in Bulma's stylesheet. The element the script
742
+ drives gets `html-extension:carousel`, below, when the codemod can read its selector.
647
743
  - **`html-script:theme`** switches Bulma's light and dark schemes. The toggle is `Theme`'s: it
648
744
  writes `data-theme` on `<html>` from its `colorMode` (`light`, `dark` or `system`), so keep the
649
745
  choice in state, save it in `localStorage`, and pass it. The first paint is `index.html`'s:
@@ -661,6 +757,40 @@ read for the burger, a carousel and a theme toggle too, and its TODO names the f
661
757
  - **`html-script`**: any other inline script, or a `<script type>` that isn't code (a template,
662
758
  JSON data). Port what it does into the component as state, effects and event handlers.
663
759
 
760
+ ### A Bulma extension's own markup: `html-extension:<name>`
761
+
762
+ A component takes the place of an extension's markup, but nothing converts that markup, so these
763
+ point at the elements to rebuild.
764
+
765
+ - **`html-extension:carousel`**: an element bulma-carousel makes a carousel of. When a script of
766
+ the page's, inline or beside it, attaches bulma-carousel with a selector written as a string of
767
+ tags, ids and classes (`bulmaCarousel.attach('#carousel-demo')`), the codemod finds every
768
+ element that selector matches, the way `querySelectorAll` does, and the TODO lists their
769
+ lines. bulma-carousel makes a carousel of each one, so a page that gives one id to two elements
770
+ has two carousels, and the TODO says so. Render a `Carousel` in the place of each, with a
771
+ `CarouselItem` for each slide (bulma-carousel 4 makes a slide of each child), and carry its
772
+ options over as `html-script:carousel` says. On a page that uses bulma-carousel, an element
773
+ with its `carousel` class that no selector reached gets the TODO too. `carousel` is also bestax
774
+ `Carousel`'s own class, so `bestax.css` and `extras.css` style that markup as `Carousel`'s root
775
+ (`position: relative`, `overflow: hidden`, `width: 100%`) until a `Carousel` takes its place. A
776
+ selector the codemod can't read (a variable, or a selector with a combinator or an attribute)
777
+ gets no TODO of its own: find what it selects by hand.
778
+ - **`html-extension:is-covered`**: bulma-carousel's `is-covered` on an `.image`, on a page that
779
+ loads its stylesheet (the bundle's combined `bulma-extensions.min.css` has no rule for it).
780
+ Inside one of the slides its script builds, that stylesheet makes the `<img>` fill the frame
781
+ and crops it to fit (`object-fit: cover`). Nothing in bestax reads the class: `Image` has no
782
+ prop for it, and the rule reaches only bulma-carousel's own slides, not `Carousel`'s. So in the
783
+ app a ratio image stretches its photo instead. Keep the class and give it the rule in the app's
784
+ stylesheet:
785
+
786
+ ```css
787
+ .image.is-covered img {
788
+ width: 100%;
789
+ height: 100%;
790
+ object-fit: cover;
791
+ }
792
+ ```
793
+
664
794
  ### `<html>` and `<body>`: `html-document:<name>`
665
795
 
666
796
  The app's document has its own `<html>` and `<body>`, which a component can't reach.
@@ -719,9 +849,9 @@ than `this`. A `<body onload>` becomes an effect.
719
849
  report entry.
720
850
 
721
851
  A link to another page beside this one should point at the route that page becomes: the
722
- router's, in an app with one. A site of static pages, with no router to join, can route on the
723
- URL's hash in a small module of its own and install no router package, so every page still
724
- opens from a static host as it is:
852
+ router's, in an app with one. A multi-page site of static pages, with no router to join, can do
853
+ its routing on the URL's hash in a small module of its own and install no router package, so
854
+ every page still opens from a static host as it is:
725
855
 
726
856
  ```ts
727
857
  // router.ts: the page the address's hash names (`#/about`).
@@ -809,8 +939,9 @@ rest.
809
939
  ### The page itself: `html-file:<case>`
810
940
 
811
941
  - **`html-file:exists`**: the module beside the page is already there, and the codemod never
812
- writes over a file. That makes a second run change nothing. Move or delete the module to convert
813
- the page again.
942
+ writes over a file. That makes a second run on the page change nothing. To carry on from the
943
+ module's TODOs, run the codemod on the module itself, which is where each TODO from the page
944
+ says to re-run ("re-run on `index.tsx`"). Move or delete the module to convert the page again.
814
945
  - **`html-file:not-included`**: a folder walk found the page, and it has Bulma classes, but a walk
815
946
  takes a page only when `--extensions` lists its extension, since most `.html` files in a React
816
947
  project aren't pages to convert (Vite's `index.html`, `public/`). `-e` matches an extension
@@ -825,6 +956,11 @@ A page with no Bulma component class writes nothing and reports nothing.
825
956
 
826
957
  - Helper classes on a `<div>` (`<div className="is-flex mt-4">`): bestax has no plain `<div>`
827
958
  wrapper, and the classes are valid Bulma, so the element stays.
959
+ - Helper classes on a table's rows and cells (`<th className="has-text-centered">`): the codemod
960
+ converts the `.table` and leaves the `<thead>`, `<tbody>`, `<tfoot>`, `<tr>`, `<th>` and `<td>`
961
+ inside it as they are. To write them by hand, `Table.Thead`, `Table.Tbody`, `Table.Tfoot`,
962
+ `Table.Tr`, `Table.Th` and `Table.Td` render each tag as it is and take the helper props, and
963
+ `Table.Th` and `Table.Td` take `isAligned` for their alignment (`<Table.Td isAligned="right">`).
828
964
  - `.help`, `.label`, `.loader` and the other classes in the component map's "left alone" list.
829
965
  - A class with no bestax prop on a converted element: it stays in `className`. That covers
830
966
  v1's `marginless` and `paddingless`, which have the same rules as `m-0` and `p-0` under
@@ -189,7 +189,7 @@ An existing `ref` is passed through untouched — which is safe only where the b
189
189
  one. rbx forwards a ref on every component; bestax does so on the form controls, plus `Avatar`,
190
190
  `Button`, `Carousel`, `CarouselItem`, `Dialog`, `Dropdown`, `EmptyState`, `Kbd`, `Kbd.Group`,
191
191
  `Link`, `LinkButton`, `Loader`, `Menu.Item`, `Modal`, `Navbar`, `Navbar.Burger`, `Navbar.Dropdown`,
192
- `Navbar.Item`, `Navbar.Link`, `SegmentedControl`, `SegmentedControl.Segment`, `Sidebar`, `Timeline`,
192
+ `Navbar.Item`, `Navbar.Link`, `ScrollArea`, `SegmentedControl`, `SegmentedControl.Segment`, `Sidebar`, `Splitter`, `Splitter.Pane`, `Timeline`,
193
193
  `Timeline.Item`, `Toast`, `ToggleButton`, `TreeView` and `TreeView.Item`. "The form controls" there
194
194
  means the inputs themselves: the `Field`, `Field.Label`, `Field.Body`, `Checkboxes` and `Radios`
195
195
  wrappers around them forward nothing, and `Field` is a target this codemod emits.
@@ -165,8 +165,8 @@ The same TODO marks a `Heading` that sets `size` and `textSize` both: drop `size
165
165
  bestax components don't take `domRef`, but many forward a plain `ref` — the form controls, plus
166
166
  `Avatar`, `Button`, `Carousel`, `CarouselItem`, `Dialog`, `Dropdown`, `EmptyState`, `Kbd`,
167
167
  `Kbd.Group`, `Link`, `LinkButton`, `Loader`, `Menu.Item`, `Modal`, `Navbar`, `Navbar.Burger`,
168
- `Navbar.Dropdown`, `Navbar.Item`, `Navbar.Link`, `SegmentedControl`, `SegmentedControl.Segment`,
169
- `Sidebar`, `Timeline`, `Timeline.Item`, `Toast`, `ToggleButton`, `TreeView` and `TreeView.Item`. On
168
+ `Navbar.Dropdown`, `Navbar.Item`, `Navbar.Link`, `ScrollArea`, `SegmentedControl`, `SegmentedControl.Segment`,
169
+ `Sidebar`, `Splitter`, `Splitter.Pane`, `Timeline`, `Timeline.Item`, `Toast`, `ToggleButton`, `TreeView` and `TreeView.Item`. On
170
170
  those, rename `domRef` to `ref` and it works; do not restructure the markup. "The form controls"
171
171
  means the inputs themselves: the `Field`, `Field.Label`, `Field.Body`, `Checkboxes` and `Radios`
172
172
  wrappers around them forward nothing, and `Field` is a target this codemod emits.
@@ -81,7 +81,9 @@ is **more complete** than the docs page's Option C example, which omits several)
81
81
  **Components** (`@allxsmith/bestax-bulma/scss/components/<name>`):
82
82
  `loading`, `collapse`, `tooltip`, `popover`, `hovercard`, `contextmenu`, `steps`, `sidebar`,
83
83
  `toast`, `dialog`, `carousel`, `tabs`, `menu`, `reveal`, `avatar`, `avatars`, `badge`,
84
- `emptystate`, `timeline`, `treeview`
84
+ `emptystate`, `timeline`, `treeview`, `scrollarea`
85
+
86
+ **Layout** (`@allxsmith/bestax-bulma/scss/layout/<name>`): `splitter`
85
87
 
86
88
  **Form** (`@allxsmith/bestax-bulma/scss/form/<name>`):
87
89
  `checkbox`, `radio`, `switch`, `slider`, `numberinput`, `pininput`, `rate`, `autocomplete`,
@@ -16,9 +16,10 @@ Recolor a brand color by overriding its **hue/saturation/lightness trio**
16
16
  follow the hue and saturation, at lightnesses the trio doesn't set. Among those, the stylesheet's
17
17
  build picked the label on a solid color (`--bulma-<color>-invert-l`) and the color's text on the
18
18
  page (`--bulma-<color>-on-scheme-l`, which for `link` is every link) for contrast with the color
19
- it was built with, so when the new color reads lighter or darker than the built one, set them in
20
- your CSS as well, or labels and text lose their contrast (`references/css-variables.md`,
21
- "Colors").
19
+ it was built with, so when the new color reads lighter or darker than the built one, set them as
20
+ well, or labels and text lose their contrast. `Theme`'s `bulmaVars` takes both, and on an
21
+ `isRoot` `Theme` the on-scheme one takes a value per scheme, `{ light, dark }`
22
+ (`references/css-variables.md`, "Colors").
22
23
 
23
24
  Choose an override path:
24
25
 
@@ -36,10 +37,12 @@ For **dark mode**, pass `colorMode` to `Theme` (`'light' | 'dark' | 'system'`).
36
37
  `data-theme` attribute on `<html>` (plus `data-<prefix>theme` inside a `ConfigProvider` with a
37
38
  `classPrefix`, which the prefixed flavors read), flipping the light/dark scheme — global, even on a scoped
38
39
  `Theme`; `'system'` follows the OS `prefers-color-scheme`. Drive it from state on the app-root
39
- `Theme`: `<Theme isRoot colorMode={mode}>`. `Theme` writes the attribute from an effect, after a
40
- client-rendered app's first paint, so the scheme of that first frame comes from `index.html`:
41
- `data-theme` on the `<html>` for a scheme that never changes, or an inline `<head>` script that
42
- writes the visitor's saved choice for a toggle (`examples/dark-mode.tsx`).
40
+ `Theme`: `<Theme isRoot colorMode={mode}>`. `Theme` writes the attribute, and an `isRoot` Theme's
41
+ variables, before the browser paints what it rendered, but the page can paint before the app's
42
+ script runs at all, so the scheme of that first frame comes from `index.html`: `data-theme` on
43
+ the `<html>` for a scheme that never changes, or an inline `<head>` script that writes the
44
+ visitor's saved choice for a toggle (`examples/dark-mode.tsx`). A server render includes neither,
45
+ so a server-rendered page sets what it needs from its first paint in `index.html` and CSS.
43
46
 
44
47
  ## Contrast rules (dark mode is on by default)
45
48
 
@@ -52,8 +55,8 @@ color tokens or fixed-color surfaces exist:
52
55
 
53
56
  - **Single-mode design → pin the scheme on `<html>`.** `<html lang="en" data-theme="light">` (or
54
57
  `"dark"`) in `index.html`, so the first paint has it and an OS preference can never invert text
55
- out from under the fixed palette. `<Theme isRoot colorMode="light">` alone sets it only after
56
- that paint, which shows the OS scheme first.
58
+ out from under the fixed palette. `<Theme isRoot colorMode="light">` alone sets it only once
59
+ the app's script runs, and the page can show the OS scheme before that.
57
60
  - **Both modes → no exposed fixed tokens.** Derive custom tokens from scheme variables
58
61
  (`--my-canvas: var(--bulma-scheme-main)`) — or flip them yourself under **both** dark-mode
59
62
  paths: `[data-theme='dark']` **and** `@media (prefers-color-scheme: dark)` scoped to
@@ -127,7 +130,7 @@ Nest `Theme` and `ConfigProvider` together at the root (order doesn't matter).
127
130
 
128
131
  ## Checklist
129
132
 
130
- - [ ] Recolor brand colors via the HSL trio (`*-h` / `*-s` / `*-l`), not by hard-coding hex on components; a color that reads lighter or darker than the built one also sets `*-invert-l` and `*-on-scheme-l` in CSS.
133
+ - [ ] Recolor brand colors via the HSL trio (`*-h` / `*-s` / `*-l`), not by hard-coding hex on components. A color that reads lighter or darker than the built one also sets `*-invert-l` and `*-on-scheme-l`, through `Theme bulmaVars` (the on-scheme one per scheme, on an `isRoot` Theme) or CSS.
131
134
  - [ ] Apply a global theme once with `<Theme isRoot>` (or `:root`); use scoped `<Theme>` for one-off sections.
132
135
  - [ ] Set non-color tokens (radius, fonts, sizes) through `bulmaVars` or `:root`; a custom `--bulma-family-*` needs its font actually loaded (`index.html` `<link>` or an `@fontsource` import).
133
136
  - [ ] Implement dark mode with `data-theme` on `<html>` (plus `data-<prefix>theme` inside a `ConfigProvider` with a `classPrefix`, which the prefixed flavors read); do not expect a shipped dark-mode component.
@@ -2,12 +2,13 @@
2
2
  // the visitor's choice.
3
3
  //
4
4
  // Bulma reads the scheme from `data-theme` on <html>. `Theme colorMode` writes
5
- // that attribute from an effect, which runs after a client-rendered app's
6
- // first paint, so on its own the page shows the OS scheme for a frame and then
7
- // flips. The scheme for that first frame comes from a script in index.html,
8
- // which writes the saved choice, or else the OS preference, before anything
9
- // renders. Put it inside <head> as a plain inline script, not
10
- // `type="module"`, so it runs before the body renders:
5
+ // that attribute before the browser paints what the app rendered, but only
6
+ // once the app's script runs, and the page can paint before that, so on its
7
+ // own it can show the OS scheme for a frame and then flip. The scheme for
8
+ // that first frame comes from a script in index.html, which writes the saved
9
+ // choice, or else the OS preference, before anything renders. Put it inside
10
+ // <head> as a plain inline script, not `type="module"`, so it runs before the
11
+ // body renders:
11
12
  //
12
13
  // <script>
13
14
  // // Bulma's scheme on <html> before the first paint: the saved choice,
@@ -12,9 +12,10 @@
12
12
  // Of those built lightnesses, the label on a solid color
13
13
  // (--bulma-<color>-invert-l) and the color's text on the page
14
14
  // (--bulma-<color>-on-scheme-l, which for `link` is every link) were picked
15
- // for contrast. A color that reads lighter or darker than the stylesheet's
16
- // own needs those set in your CSS too, which Theme does not do;
17
- // references/css-variables.md ("Colors") says how.
15
+ // for contrast with the stylesheet's own color. A color that reads lighter or
16
+ // darker than that one sets them too, through bulmaVars. On an isRoot Theme
17
+ // the on-scheme one takes a value for each scheme. references/css-variables.md
18
+ // ("Colors") says how to pick them.
18
19
  import type { ReactNode } from 'react';
19
20
  import {
20
21
  Theme,
@@ -37,6 +38,12 @@ export function ThemedApp({ children }: { children: ReactNode }) {
37
38
  // Anything without a named prop goes through `bulmaVars`, keyed by the real
38
39
  // --bulma-* variable names (string values).
39
40
  bulmaVars={{
41
+ // Purple text reads darker than the stylesheet's blue at the same
42
+ // lightness, so on a dark page it needs a lighter shade than the one
43
+ // built for the blue. Each scheme gets its own, and these read at 4.5:1
44
+ // or more on Bulma's white and near-black pages. The white label the
45
+ // blue was built with still reads on this purple, so the invert stays.
46
+ '--bulma-primary-on-scheme-l': { light: '41%', dark: '65%' },
40
47
  '--bulma-radius': '0.75rem',
41
48
  '--bulma-radius-large': '1.25rem',
42
49
  // 'Inter' must actually be loaded (index.html <link> or an @fontsource