create-bestax 4.2.18 → 4.2.19

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 (38) hide show
  1. package/dist/constants.d.ts.map +1 -1
  2. package/dist/constants.js +4 -1
  3. package/dist/file-system.d.ts +5 -1
  4. package/dist/file-system.d.ts.map +1 -1
  5. package/dist/file-system.js +11 -1
  6. package/dist/library-range.d.ts +16 -0
  7. package/dist/library-range.d.ts.map +1 -0
  8. package/dist/library-range.js +40 -0
  9. package/dist/project-creator.d.ts.map +1 -1
  10. package/dist/project-creator.js +4 -1
  11. package/package.json +7 -7
  12. package/templates/skills/bestax-custom-component/references/component-catalog.md +3 -1
  13. package/templates/skills/bestax-form/SKILL.md +40 -15
  14. package/templates/skills/bestax-form/references/api.md +10 -8
  15. package/templates/skills/bestax-form/references/patterns.md +68 -0
  16. package/templates/skills/bestax-layout-scaffold/SKILL.md +16 -5
  17. package/templates/skills/bestax-layout-scaffold/references/archetypes.md +2 -2
  18. package/templates/skills/bestax-layout-scaffold/references/layout-components.md +52 -5
  19. package/templates/skills/bestax-migrate/SKILL.md +11 -3
  20. package/templates/skills/bestax-migrate/references/bloomer/component-map.md +1 -1
  21. package/templates/skills/bestax-migrate/references/bloomer/prop-map.md +8 -5
  22. package/templates/skills/bestax-migrate/references/bulma-classes/component-map.md +144 -101
  23. package/templates/skills/bestax-migrate/references/bulma-classes/prop-map.md +47 -16
  24. package/templates/skills/bestax-migrate/references/bulma-classes/unmappables.md +217 -45
  25. package/templates/skills/bestax-migrate/references/css-migration.md +12 -5
  26. package/templates/skills/bestax-migrate/references/rbx/prop-map.md +19 -13
  27. package/templates/skills/bestax-migrate/references/react-bulma-components/prop-map.md +27 -22
  28. package/templates/skills/bestax-migrate/references/react-bulma-components/unmappables.md +1 -1
  29. package/templates/skills/bestax-optimize/references/modular-build.md +57 -17
  30. package/templates/skills/bestax-theming/SKILL.md +19 -8
  31. package/templates/skills/bestax-theming/examples/dark-mode.tsx +99 -17
  32. package/templates/skills/bestax-theming/examples/theme-config.tsx +13 -8
  33. package/templates/skills/bestax-theming/references/css-variables.md +188 -18
  34. package/templates/skills/bestax-theming/references/themeable-components.md +2 -0
  35. package/templates/vite/package.json +1 -1
  36. package/templates/vite/src/main.jsx +1 -1
  37. package/templates/vite-ts/package.json +1 -1
  38. package/templates/vite-ts/src/main.tsx +1 -1
@@ -39,12 +39,16 @@ Every component also accepts the shared Bulma helper props (`m`/`p` spacing, `te
39
39
 
40
40
  | Prop | Type | Notes |
41
41
  | --------------------------------- | --------------------------------------- | ----------------------------------------- |
42
+ | `as` | `'div'` (default) or a landmark tag | Renders that tag in place of the `div`. |
42
43
  | `fluid` | `boolean` | Full width with a small gutter. |
43
44
  | `widescreen` / `fullhd` | `boolean` | Only constrain at that breakpoint and up. |
44
45
  | `breakpoint` | `'tablet' \| 'desktop' \| 'widescreen'` | Max-width breakpoint. |
45
46
  | `isMax` | `boolean` | Use the `is-max-*` width. |
46
47
  | `color` / `textColor` / `bgColor` | Bulma color | — |
47
48
 
49
+ `as` takes `'main' | 'section' | 'article' | 'aside' | 'header' | 'footer' | 'nav'`, so
50
+ `<Container as="main">` renders `<main class="container">` with no wrapper.
51
+
48
52
  ## Section
49
53
 
50
54
  `<Section>` adds vertical page rhythm (padding).
@@ -63,6 +67,7 @@ Every component also accepts the shared Bulma helper props (`m`/`p` spacing, `te
63
67
  | `color` | `primary \| link \| info \| success \| warning \| danger \| black \| white \| light \| dark` (CSS-backed). Also accepted but deprecated, no CSS, dev-warn: every other `validColors` member, plus `inherit` and `current` |
64
68
  | `size` | `'small' \| 'medium' \| 'large' \| 'fullheight' \| 'fullheight-with-navbar'` |
65
69
  | `fullheightWithNavbar` | `boolean` |
70
+ | `as` | `'section'` (default) \| `'div'` \| `'main'` \| `'header'` \| `'footer'` \| `'article'` \| `'aside'` |
66
71
 
67
72
  ```tsx
68
73
  <Hero color="primary" size="medium">
@@ -70,6 +75,11 @@ Every component also accepts the shared Bulma helper props (`m`/`p` spacing, `te
70
75
  </Hero>
71
76
  ```
72
77
 
78
+ `Hero.Head` takes `as="header"` or `as="nav"` and `Hero.Foot` `as="footer"` or `as="nav"` (both
79
+ `div` by default), so a page that is all hero is `<Hero as="main">` with no wrapper. A `header` or
80
+ `footer` is the page's banner or footer landmark only outside a `section`, `article`, `aside`,
81
+ `nav` or `main`, so for that render the hero itself `as="div"`.
82
+
73
83
  ## Footer
74
84
 
75
85
  `<Footer>` is the page footer.
@@ -230,6 +240,8 @@ flexDirection="column"` + `flexGrow="1"` pattern applies to `Cell` + `Card`.
230
240
  | --------------- | ---------------------------------------------------------------------------------------------------------------- |
231
241
  | `fixed` | `'top' \| 'bottom'` |
232
242
  | `transparent` | `boolean` |
243
+ | `isSpaced` | `boolean` (Bulma's `is-spaced`, from desktop up) |
244
+ | `hasShadow` | `boolean` (default `false`, unlike `Card`'s; Bulma's `has-shadow`) |
233
245
  | `color` | `'primary' \| 'link' \| 'info' \| 'success' \| 'warning' \| 'danger' \| 'black' \| 'dark' \| 'light' \| 'white'` |
234
246
  | `Navbar.Item` | `as?`, `active?`, `href` |
235
247
  | `Navbar.Burger` | `active?`, `onClick` |
@@ -239,7 +251,12 @@ The burger/menu is controlled state — toggle `Navbar.Burger active`/`onClick`
239
251
  flag to `Navbar.Menu active`. **A `fixed="top"` navbar requires `has-navbar-fixed-top` on `<html>`**
240
252
  (Bulma offsets the page from it); the library adds no helper. Set it statically —
241
253
  `<html class="has-navbar-fixed-top">` in `index.html` — reserving a `classList.add` effect for
242
- a navbar that mounts conditionally.
254
+ a navbar that mounts conditionally. **With `isSpaced`, add `has-spaced-navbar-fixed-top` too**
255
+ (`<html class="has-navbar-fixed-top has-spaced-navbar-fixed-top">`): from desktop up the spaced
256
+ navbar is taller than the plain class pads the page for, and there the spaced class pads it for
257
+ the taller navbar, so with the plain class alone the top of the page sits under the navbar. A
258
+ `fixed="bottom"` navbar takes `has-navbar-fixed-bottom`, and `has-spaced-navbar-fixed-bottom`
259
+ beside it with `isSpaced`.
243
260
 
244
261
  **Routing:** in a routed app, don't use `href="#"` — render items as the router's link
245
262
  component. `Menu.Item as={Link} to="/x"` and `Navbar.Item as={Link} to="/x"` both compile
@@ -250,11 +267,13 @@ https://bestax.io/docs/guides/features/routing.
250
267
 
251
268
  ## Menu
252
269
 
253
- `<Menu>` is a vertical sidebar menu. Subcomponents: `Menu.Label`, `Menu.List`, `Menu.Item`.
270
+ `<Menu>` is a vertical sidebar menu. Subcomponents: `Menu.Label`, `Menu.List`, `Menu.Item`,
271
+ `Menu.Section`.
254
272
 
255
- | Prop | Type |
256
- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
257
- | `Menu.Item` | `active?` (the current page: `is-active` plus `aria-current="page"`, so don't add your own), `href?`, `as?` (custom link component); nest a `Menu.List` inside an item for submenus |
273
+ | Prop | Type |
274
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
275
+ | `Menu.Item` | `active?` (the current page: `is-active` plus `aria-current="page"`, so don't add your own), `href?`, `as?` (custom link component); nest a `Menu.List` inside an item for submenus |
276
+ | `Menu.Section` | `label` (the heading, a `ReactNode`), `defaultOpen?`, or `open?` with `onOpenChange?(open)` to hold the state yourself, as `Collapse` does; its child is the group's `Menu.List`, and a closed section's links are out of reach |
258
277
 
259
278
  ```tsx
260
279
  <Menu>
@@ -268,6 +287,34 @@ https://bestax.io/docs/guides/features/routing.
268
287
  </Menu>
269
288
  ```
270
289
 
290
+ A sidebar whose groups of links open and close (a docs sidebar, an admin app's sections) puts each
291
+ group's `Menu.List` in a `Menu.Section`. Its heading is a real button drawn like `Menu.Label`, with
292
+ `aria-expanded` and a chevron, and its links stay ordinary links, each a tab stop. Open the
293
+ section that holds the current page. A section goes directly in `Menu`, never inside a `Menu.Item`
294
+ (it would render inside the item's link) or a `Menu.List` (it isn't a list item). Don't build this
295
+ from `TreeView` (its items aren't links) or from a `Collapse` in a hand-written `<li>` (its trigger
296
+ is a `div` with `role="button"`).
297
+
298
+ ```tsx
299
+ <nav aria-label="Documentation">
300
+ <Menu>
301
+ <Menu.Section label="Getting started">
302
+ <Menu.List>
303
+ <Menu.Item href="/docs/install">Installation</Menu.Item>
304
+ </Menu.List>
305
+ </Menu.Section>
306
+ <Menu.Section label="Components" defaultOpen>
307
+ <Menu.List>
308
+ <Menu.Item href="/docs/button" active>
309
+ Button
310
+ </Menu.Item>
311
+ <Menu.Item href="/docs/card">Card</Menu.Item>
312
+ </Menu.List>
313
+ </Menu.Section>
314
+ </Menu>
315
+ </nav>
316
+ ```
317
+
271
318
  ## Card
272
319
 
273
320
  `<Card>` for catalog/grid items. Use the props form (`header`, `image`, `footer`) or the compound
@@ -57,6 +57,10 @@ Run these steps in order. Don't hand-convert what the codemod converts automatic
57
57
  entry, and port each script's work into the component. Under `--css bestax` or `--css bulma`
58
58
  the module imports the stylesheet in place of the page's Bulma `<link>`.
59
59
 
60
+ A file that fails to parse or transform is marked `✖`, listed under `failed` in the report
61
+ and left as it was, and the run exits 1 while every other file still converts. Fix that file
62
+ and run the codemod on it again. TODOs alone leave the exit code at 0.
63
+
60
64
  2. **Apply it** (same command without `--dry`), then run the project's formatter — the
61
65
  codemod preserves surrounding formatting but doesn't prettify what it rewrites.
62
66
  Besides the components, it also migrates **stylesheets** (CSS imports →
@@ -132,9 +136,13 @@ code per the references, or deliberately keep the old markup with `className` st
132
136
  drift than the 0.9 → 1 guide alone describes.
133
137
  - **bulma-classes**: there is no library to remove. An element converts only when the bestax
134
138
  component renders the same markup (tag, classes, attributes), so a converted page renders
135
- the same HTML; everything else stays as written, with a TODO when there is something to
136
- decide. Static class strings convert, and so does a `clsx` or `classnames` call of strings
137
- and conditional classes, where a condition on a flag becomes its prop (`isLoading={busy}`);
139
+ the same HTML, but for deliberate exceptions: Font Awesome's own hidden `<i>` in a `.icon`
140
+ that carries nothing else assistive tech reads becomes `Icon`, which hides the `.icon`
141
+ instead, the glyph hidden either way, and Bulma 0.9's `is-marginless` and `is-paddingless`,
142
+ which style nothing under v1, become `m="0"` and `p="0"`. Everything else stays as written, with a
143
+ TODO when there is something to decide. Static class strings convert, and so does a `clsx` or
144
+ `classnames` call of strings and conditional classes, where a condition on a flag becomes
145
+ its prop (`isLoading={busy}`);
138
146
  any other computed className gets `dynamic-class:<Target>`, naming the component the element
139
147
  would become. Files a Next.js
140
148
  App Router may render as server components are left alone (`rsc`), and the manifest gains
@@ -151,7 +151,7 @@ Some targets depend on a prop or a child, so the codemod picks them element by e
151
151
  | `PageControl` | `Pagination.Previous` / `Pagination.Next` | `isNext` (bloomer's default is previous) |
152
152
  | `NavbarItem` | `Navbar.Item` / `Navbar.Dropdown` | `hasDropdown` |
153
153
  | `Page` | _folded into its child_ / `<li>` | whether the child is a `PageLink`/`PageEllipsis`; attributes on the Page move onto the link, with a `component:Page` note |
154
- | `PanelBlock` | `Panel.Block` / `<div class="panel-block">` | `href` — bestax's block is always an `<a>` |
154
+ | `PanelBlock` | `Panel.Block` / _the plain block_ | the element: an `<a>`, a `<div>` or a `<label>` is the component; any other `tag`, or a dynamic one, stays plain markup |
155
155
  | `MenuLink` in a literal `<li>` | `Menu.Item`, the `<li>` folded away | bestax's item renders its own `<li>` |
156
156
  | `Breadcrumb` around a literal `<ul>` | `Breadcrumb`, the `<ul>` folded away | bestax's breadcrumb renders its own `<ul>` |
157
157
  | `Icon`, `PanelIcon` | `name`/`library`/`variant` / an `<i>` child | whether the icon classes are FA5/6 or MDI |
@@ -119,8 +119,8 @@ narrowed to a literal union, so a value outside the union is a type error you ca
119
119
  than a silent rewrite. The components whose `tag` becomes `as`:
120
120
 
121
121
  `Button`, `Image`, `Title`, `Subtitle`, `Footer`, `Media`, `MediaLeft`, `LevelItem`, `Control`,
122
- `DropdownItem`,
123
- `MenuLink`, `NavbarItem`, `NavbarLink`.
122
+ `DropdownItem`, `MenuLink`, `NavbarItem`, `NavbarLink`, `Container`, `Hero`, `HeroHeader`,
123
+ `HeroFooter`, `Panel`.
124
124
 
125
125
  Everywhere else `tag` is left in place with a TODO. On the components that become plain markup
126
126
  (`Help`, `Label`, `Heading`, `BreadcrumbItem`, `PanelTab`, `TabLink`, `Page`, `HeroVideo`) a
@@ -148,9 +148,12 @@ rendered. `Button` and `LevelItem` gain `as="a"`; on the targets that already re
148
148
  stays — each of those takes the anchor's props under its default `as`. A `NavbarItem` or
149
149
  `DropdownItem` with neither `href` nor `tag` gains `as="div"`, because bestax's `Navbar.Item`
150
150
  and `Dropdown.Item` default to an `<a>`. An empty or false `href` selected nothing in bloomer
151
- and is dropped. bestax's `Panel.Block` is always an `<a>`, so only a `PanelBlock` with `href`
152
- becomes one — the rest stay the plain `<div class="panel-block">` (or the `tag` you gave) that
153
- bloomer rendered.
151
+ and is dropped. bestax's `Panel.Block` is an `<a>` by default and a `<div>` or a `<label>`
152
+ through `as`, so a `PanelBlock` with no `href` becomes `<Panel.Block as="div">`, or
153
+ `as="label"` for `tag="label"`, and one with `tag="a"` a bare `<Panel.Block>`. One on any
154
+ other `tag` stays the plain block, on that tag, that bloomer rendered. A dynamic `tag` becomes
155
+ a plain `<div class="panel-block">` with a `prop:tag` TODO, since the codemod can't tell which
156
+ tag it names.
154
157
 
155
158
  A dynamic `href={expr}` was a runtime decision bloomer made and bestax's `as` is one element or
156
159
  the other. It resolves to the anchor, which is the branch the `href` is there for, and the
@@ -4,7 +4,11 @@ For apps that style plain JSX with Bulma's classes (`<div className="columns">`)
4
4
  wrapper library. The codemod converts an element only when the bestax component renders
5
5
  **exactly the markup the element did**: same tag, same classes, same attributes. Anything else
6
6
  stays as written, with a `TODO(bestax-migrate)` when there is something to decide (see
7
- [unmappables.md](unmappables.md)).
7
+ [unmappables.md](unmappables.md)). The exceptions are deliberate. Font Awesome's own
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).
8
12
 
9
13
  A class the codemod doesn't know, such as the app's own `pricing-card`, rides along in
10
14
  `className`, which every bestax component passes through. So does a Bulma class with no bestax
@@ -17,98 +21,104 @@ test holds it to that table. Render tests hold the table to the library itself.
17
21
  ## Components
18
22
 
19
23
  The **Tags** column is what the codemod converts the class on: an element on any other tag gets
20
- a `tag:<Target>` TODO instead of a conversion. A component's own `as` can take more. One exception:
21
- a `<label>` or `<div>` `.panel-block` stays markup with no TODO, since bestax renders those as
22
- `Panel.CheckboxBlock`, `Panel.InputBlock` and `Panel.ButtonBlock`, which build their own contents.
23
-
24
- | Bulma class | bestax-bulma | Tags |
25
- | ---------------------- | --------------------- | -------------------------------------------------------------- |
26
- | `.block` | `Block` | `<div>` only |
27
- | `.box` | `Box` | `<div>` only |
28
- | `.button` | `Button` | `<button>`, any tag via `as` |
29
- | `.buttons` | `Buttons` | `<div>` only |
30
- | `.card` | `Card` | `<div>` only |
31
- | `.card-header` | `Card.Header` | `<header>` only |
32
- | `.card-header-title` | `Card.Header.Title` | `<div>`, `<p>`, `<h2>`, `<h3>`, `<h4>` via `as` |
33
- | `.card-header-icon` | `Card.Header.Icon` | `<button>` only |
34
- | `.card-image` | `Card.Image` | `<div>` only |
35
- | `.card-content` | `Card.Content` | `<div>` only |
36
- | `.card-footer` | `Card.Footer` | `<footer>` only |
37
- | `.card-footer-item` | `Card.FooterItem` | `<span>`, `<a>`, `<button>` via `as` |
38
- | `.columns` | `Columns` | `<div>` only |
39
- | `.column` | `Column` | `<div>` only |
40
- | `.grid` | `Grid` | `<div>` only |
41
- | `.cell` | `Cell` | `<div>` only |
42
- | `.container` | `Container` | `<div>` only |
43
- | `.content` | `Content` | `<div>` only |
44
- | `.delete` | `Delete` | `<button>` only |
45
- | `.field` | `Field` | `<div>` only |
46
- | `.field-label` | `Field.Label` | `<div>` only |
47
- | `.field-body` | `Field.Body` | `<div>` only |
48
- | `.control` | `Control` | `<div>`, `<p>` via `as` |
49
- | `.input` | `InputBase` | `<input>` only |
50
- | `.textarea` | `TextAreaBase` | `<textarea>` only |
51
- | `.select` | `SelectBase` | `<div>` only |
52
- | `.footer` | `Footer` | `<footer>`, `<div>` via `as` |
53
- | `.hero` | `Hero` | `<section>` only |
54
- | `.hero-head` | `Hero.Head` | `<div>` only |
55
- | `.hero-body` | `Hero.Body` | `<div>` only |
56
- | `.hero-foot` | `Hero.Foot` | `<div>` only |
57
- | `.level` | `Level` | `<nav>` only |
58
- | `.level-left` | `Level.Left` | `<div>` only |
59
- | `.level-right` | `Level.Right` | `<div>` only |
60
- | `.level-item` | `Level.Item` | `<div>`, `<p>`, `<a>` via `as` |
61
- | `.media` | `Media` | `<article>`, `<div>` via `as` |
62
- | `.media-left` | `Media.Left` | `<figure>`, `<div>` via `as` |
63
- | `.media-content` | `Media.Content` | `<div>` only |
64
- | `.media-right` | `Media.Right` | `<div>` only |
65
- | `.navbar` | `Navbar` | `<nav>` only |
66
- | `.navbar-brand` | `Navbar.Brand` | `<div>` only |
67
- | `.navbar-menu` | `Navbar.Menu` | `<div>` only |
68
- | `.navbar-start` | `Navbar.Start` | `<div>` only |
69
- | `.navbar-end` | `Navbar.End` | `<div>` only |
70
- | `.navbar-item` | `Navbar.Item` | `<a>`, any tag via `as` |
71
- | `.navbar-dropdown` | `Navbar.DropdownMenu` | `<div>` only |
72
- | `.navbar-divider` | `Navbar.Divider` | `<hr>` only |
73
- | `.notification` | `Notification` | `<div>` only |
74
- | `.progress` | `Progress` | `<progress>` only |
75
- | `.section` | `Section` | `<section>` only |
76
- | `.subtitle` | `SubTitle` | `<h1>`, `<h2>`, `<h3>`, `<h4>`, `<h5>`, `<h6>`, `<p>` via `as` |
77
- | `.table` | `Table` | `<table>` only |
78
- | `.tag` | `Tag` | `<span>` only |
79
- | `.tags` | `Tags` | `<div>` only |
80
- | `.title` | `Title` | `<h1>`, `<h2>`, `<h3>`, `<h4>`, `<h5>`, `<h6>`, `<p>` via `as` |
81
- | `.breadcrumb` | `Breadcrumb` | `<nav>` only |
82
- | `.skeleton-block` | `Skeleton` | `<div>` only |
83
- | `.skeleton-lines` | `Skeleton` | `<div>` only |
84
- | `.file` | `File` | `<div>` only |
85
- | `.icon` | `Icon` | `<span>` only |
86
- | `.icon-text` | `IconText` | `<span>` only |
87
- | `.image` | `Image` | `<div>`, `<figure>`, `<p>` via `as` |
88
- | `.menu` | `Menu` | `<aside>` only |
89
- | `.menu-label` | `Menu.Label` | `<p>` only |
90
- | `.menu-list` | `Menu.List` | `<ul>` only |
91
- | `.modal-background` | `Modal.Background` | `<div>` only |
92
- | `.modal-content` | `Modal.Content` | `<div>` only |
93
- | `.modal-card` | `Modal.Card` | `<div>` only |
94
- | `.modal-card-head` | `Modal.Card.Head` | `<header>` only |
95
- | `.modal-card-title` | `Modal.Card.Title` | `<p>` only |
96
- | `.modal-card-body` | `Modal.Card.Body` | `<section>` only |
97
- | `.modal-card-foot` | `Modal.Card.Foot` | `<footer>` only |
98
- | `.pagination` | `Pagination` | `<nav>` only |
99
- | `.pagination-list` | `Pagination.List` | `<ul>` only |
100
- | `.pagination-previous` | `Pagination.Previous` | `<a>` only |
101
- | `.pagination-next` | `Pagination.Next` | `<a>` only |
102
- | `.pagination-link` | `Pagination.Link` | `<a>` only |
103
- | `.pagination-ellipsis` | `Pagination.Ellipsis` | `<span>` only |
104
- | `.panel` | `Panel` | `<nav>` only |
105
- | `.panel-heading` | `Panel.Heading` | `<p>` only |
106
- | `.panel-tabs` | `Panel.Tabs` | `<p>` only |
107
- | `.panel-block` | `Panel.Block` | `<a>` only |
108
- | `.tabs` | `Tabs` | `<div>` only |
109
-
110
- An element with two of these (`<div className="column box">`) becomes the layout one
111
- (`Column`), and the other class stays in `className`.
24
+ a `tag:<Target>` TODO instead of a conversion. A component's own `as` can take more.
25
+
26
+ | Bulma class | bestax-bulma | Tags |
27
+ | ---------------------- | --------------------- | ------------------------------------------------------------------------------------------------ |
28
+ | `.block` | `Block` | `<div>` only |
29
+ | `.box` | `Box` | `<div>` only |
30
+ | `.button` | `Button` | `<button>`, any tag via `as` |
31
+ | `.buttons` | `Buttons` | `<div>` only |
32
+ | `.card` | `Card` | `<div>` only |
33
+ | `.card-header` | `Card.Header` | `<header>` only |
34
+ | `.card-header-title` | `Card.Header.Title` | `<div>`, `<p>`, `<h2>`, `<h3>`, `<h4>` via `as` |
35
+ | `.card-header-icon` | `Card.Header.Icon` | `<button>` only |
36
+ | `.card-image` | `Card.Image` | `<div>` only |
37
+ | `.card-content` | `Card.Content` | `<div>` only |
38
+ | `.card-footer` | `Card.Footer` | `<footer>` only |
39
+ | `.card-footer-item` | `Card.FooterItem` | `<span>`, `<a>`, `<button>` via `as` |
40
+ | `.columns` | `Columns` | `<div>` only |
41
+ | `.column` | `Column` | `<div>` only |
42
+ | `.grid` | `Grid` | `<div>` only |
43
+ | `.cell` | `Cell` | `<div>` only |
44
+ | `.container` | `Container` | `<div>`, `<main>`, `<section>`, `<article>`, `<aside>`, `<header>`, `<footer>`, `<nav>` via `as` |
45
+ | `.content` | `Content` | `<div>` only |
46
+ | `.delete` | `Delete` | `<button>` only |
47
+ | `.field` | `Field` | `<div>` only |
48
+ | `.field-label` | `Field.Label` | `<div>` only |
49
+ | `.field-body` | `Field.Body` | `<div>` only |
50
+ | `.control` | `Control` | `<div>`, `<p>` via `as` |
51
+ | `.input` | `InputBase` | `<input>` only |
52
+ | `.textarea` | `TextAreaBase` | `<textarea>` only |
53
+ | `.select` | `SelectBase` | `<div>` only |
54
+ | `.footer` | `Footer` | `<footer>`, `<div>` via `as` |
55
+ | `.hero` | `Hero` | `<section>`, `<div>`, `<main>`, `<header>`, `<footer>`, `<article>`, `<aside>` via `as` |
56
+ | `.hero-head` | `Hero.Head` | `<div>`, `<header>`, `<nav>` via `as` |
57
+ | `.hero-body` | `Hero.Body` | `<div>` only |
58
+ | `.hero-foot` | `Hero.Foot` | `<div>`, `<footer>`, `<nav>` via `as` |
59
+ | `.level` | `Level` | `<nav>` only |
60
+ | `.level-left` | `Level.Left` | `<div>` only |
61
+ | `.level-right` | `Level.Right` | `<div>` only |
62
+ | `.level-item` | `Level.Item` | `<div>`, `<p>`, `<a>` via `as` |
63
+ | `.media` | `Media` | `<article>`, `<div>` via `as` |
64
+ | `.media-left` | `Media.Left` | `<figure>`, `<div>` via `as` |
65
+ | `.media-content` | `Media.Content` | `<div>` only |
66
+ | `.media-right` | `Media.Right` | `<div>` only |
67
+ | `.navbar` | `Navbar` | `<nav>` only |
68
+ | `.navbar-brand` | `Navbar.Brand` | `<div>` only |
69
+ | `.navbar-menu` | `Navbar.Menu` | `<div>` only |
70
+ | `.navbar-start` | `Navbar.Start` | `<div>` only |
71
+ | `.navbar-end` | `Navbar.End` | `<div>` only |
72
+ | `.navbar-item` | `Navbar.Item` | `<a>`, any tag via `as` |
73
+ | `.navbar-dropdown` | `Navbar.DropdownMenu` | `<div>` only |
74
+ | `.navbar-divider` | `Navbar.Divider` | `<hr>` only |
75
+ | `.notification` | `Notification` | `<div>` only |
76
+ | `.progress` | `Progress` | `<progress>` only |
77
+ | `.section` | `Section` | `<section>` only |
78
+ | `.subtitle` | `SubTitle` | `<h1>`, `<h2>`, `<h3>`, `<h4>`, `<h5>`, `<h6>`, `<p>` via `as` |
79
+ | `.table` | `Table` | `<table>` only |
80
+ | `.tag` | `Tag` | `<span>` only |
81
+ | `.tags` | `Tags` | `<div>` only |
82
+ | `.title` | `Title` | `<h1>`, `<h2>`, `<h3>`, `<h4>`, `<h5>`, `<h6>`, `<p>` via `as` |
83
+ | `.breadcrumb` | `Breadcrumb` | `<nav>` only |
84
+ | `.skeleton-block` | `Skeleton` | `<div>` only |
85
+ | `.skeleton-lines` | `Skeleton` | `<div>` only |
86
+ | `.file` | `File` | `<div>` only |
87
+ | `.icon` | `Icon` | `<span>` only |
88
+ | `.icon-text` | `IconText` | `<span>` only |
89
+ | `.image` | `Image` | `<div>`, `<figure>`, `<p>` via `as` |
90
+ | `.menu` | `Menu` | `<aside>` only |
91
+ | `.menu-label` | `Menu.Label` | `<p>` only |
92
+ | `.menu-list` | `Menu.List` | `<ul>` only |
93
+ | `.modal-background` | `Modal.Background` | `<div>` only |
94
+ | `.modal-content` | `Modal.Content` | `<div>` only |
95
+ | `.modal-card` | `Modal.Card` | `<div>` only |
96
+ | `.modal-card-head` | `Modal.Card.Head` | `<header>` only |
97
+ | `.modal-card-title` | `Modal.Card.Title` | `<p>` only |
98
+ | `.modal-card-body` | `Modal.Card.Body` | `<section>` only |
99
+ | `.modal-card-foot` | `Modal.Card.Foot` | `<footer>` only |
100
+ | `.pagination` | `Pagination` | `<nav>` only |
101
+ | `.pagination-list` | `Pagination.List` | `<ul>` only |
102
+ | `.pagination-previous` | `Pagination.Previous` | `<a>` only |
103
+ | `.pagination-next` | `Pagination.Next` | `<a>` only |
104
+ | `.pagination-link` | `Pagination.Link` | `<a>` only |
105
+ | `.pagination-ellipsis` | `Pagination.Ellipsis` | `<span>` only |
106
+ | `.panel` | `Panel` | `<nav>`, `<article>`, `<section>`, `<aside>`, `<div>` via `as` |
107
+ | `.panel-heading` | `Panel.Heading` | `<p>` only |
108
+ | `.panel-tabs` | `Panel.Tabs` | `<p>` only |
109
+ | `.panel-block` | `Panel.Block` | `<a>`, `<div>`, `<label>` via `as` |
110
+ | `.tabs` | `Tabs` | `<div>` only |
111
+
112
+ An element with two of these (`<div className="column box">`) becomes the one whose component
113
+ fits its tag best, and the other class stays in `className` with a `second-component:<Target>`
114
+ TODO. A component that renders the tag with no `as` beats one that reaches it only through `as`,
115
+ which beats one that can't render it, and between equals the layout one wins (`Column`). So
116
+ `<section className="hero container">` becomes a `Hero`, and `<figure className="image container">`
117
+ an `Image`, which `Container` can't render. The tag a component renders with no `as` is the one
118
+ the element's classes give it: a ratio puts `Image` on a `<figure>`, so
119
+ `<figure className="navbar-item image is-16by9">` becomes an `Image`, and
120
+ `<div className="media-left image is-1by1">` a `Media.Left`, since both reach a `<div>` only
121
+ through `as` there. A heading's size does the same for `Title` and `SubTitle`.
112
122
 
113
123
  `Card` renders its children inside a `.card-content` of its own unless one of them is one of
114
124
  its parts, and `Card.Header` its children inside a `.card-header-title` unless one of them is a
@@ -144,9 +154,36 @@ component, which could hand it a labelled Field's generated one.
144
154
  A `.tabs` converts around its `<ul>`, and the `<li>`s and `<a>`s inside stay as written: `Tabs.Tab`
145
155
  renders its own `<a>` with tab roles and puts its label in a `<span>`, so converting the tabs
146
156
  themselves is by hand (something that sat beside the text in the `<a>`, a `Tag` say, then wants a
147
- `Span display="flex" alignItems="center"` around it and the label). A `.icon` converts around the
148
- `<i>` inside it when it carries the attribute `Icon` writes otherwise: `aria-hidden` with no name,
149
- no `role` and no `tabIndex`, and `role="img"` beside an `aria-label` or `aria-labelledby`.
157
+ `Span display="flex" alignItems="center"` around it and the label). A `.icon` converts when it
158
+ 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:
162
+
163
+ ```jsx
164
+ <span className="icon is-small" aria-hidden="true">
165
+ <i className="fab fa-github fa-lg"></i>
166
+ </span>
167
+ // becomes
168
+ <Icon library="fa" name="github" variant="brands" features="fa-lg" size="small" aria-hidden="true" />
169
+ ```
170
+
171
+ Font Awesome's own markup hides the `<i>` instead, and converts the same way when the `.icon`
172
+ carries nothing but its classes, an `id`, a `style` and `data-` attributes: the `aria-hidden`
173
+ moves up onto the `.icon`, where `Icon` writes it, so the glyph is hidden either way. That is a
174
+ place a conversion changes the markup, on purpose:
175
+
176
+ ```jsx
177
+ <span className="icon is-small is-left">
178
+ <i className="fas fa-envelope" aria-hidden="true"></i>
179
+ </span>
180
+ // becomes, rendering aria-hidden="true" on the .icon
181
+ <Icon library="fa" name="envelope" size="small" className="is-left" />
182
+ ```
183
+
184
+ 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.
150
187
 
151
188
  A `.menu` converts with its `.menu-label`s and `.menu-list`s, and so do the items in a list. An
152
189
  item has no class to go by, so the codemod finds it by where it sits: a `<li>` whose nearest
@@ -243,7 +280,8 @@ go straight inside `Breadcrumb`, and `<figure className="image is-64x64"><img sr
243
280
  one, so the `<ul>` carries nothing and the `.breadcrumb` needs an `aria-label` of its own.
244
281
  - `Image` renders its `<img>` from `src` and `alt`, so the `<img>` carries nothing else, and
245
282
  `is-rounded` on it becomes `isRounded`. The `.image` keeps its own attributes. A ratio
246
- (`is-4by3`) stays a class, since `size` would add `has-ratio` as well. Around anything else (an
283
+ (`is-4by3`) becomes `size` too. `Image` renders a ratio on a `<figure>`, so a `<figure>` needs no
284
+ `as` there and a `<div>` keeps `as="div"`. Around anything else (an
247
285
  `<img>` with more attributes, an `<iframe>`), `Image` renders its children as given, so the
248
286
  `.image` converts around them and they stay as written, as long as they're HTML elements written
249
287
  out. An empty `.image` stays markup, since `Image` would render an `<img>` of its own, and so does
@@ -345,7 +383,12 @@ button, the navbar's burger and dropdown link, the panel's icon, Radio and Radio
345
383
  ## Classes left alone
346
384
 
347
385
  `.help`, `.label`, `.hero-buttons`, `.hero-video`, `.theme-dark`, `.theme-light`, `.fa`,
348
- `.marginless`, `.paddingless`, `.navbar-content`, `.navbar-tabs` and `.panel-list` are valid
349
- Bulma with nothing in bestax to convert to. `.loader` is left alone as well, although bestax's
350
- `Loader` renders the same element: swap it in by hand for its progressbar role and accessible name.
351
- An element carrying one stays as written, and gets no TODO.
386
+ `.navbar-content`, `.navbar-tabs` and `.panel-list` are valid Bulma with nothing in bestax to
387
+ convert to. `.loader` is left alone as well, although bestax's `Loader` renders the same
388
+ element: swap it in by hand for its progressbar role and accessible name. An element carrying
389
+ one stays as written, and gets no TODO.
390
+
391
+ v1's `marginless` and `paddingless` are helpers, not components, so they don't hold an element
392
+ back: `<p className="has-text-centered marginless">` becomes
393
+ `<Paragraph textAlign="centered" className="marginless">`. They keep their names, since bestax
394
+ writes the same rules as `m-0` and `p-0`.