create-bestax 3.7.0 → 3.8.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.
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +41 -3
- package/package.json +1 -1
- package/templates/skills/bestax-custom-component/SKILL.md +46 -9
- package/templates/skills/bestax-custom-component/examples/stat-card.tsx +5 -1
- package/templates/skills/bestax-custom-component/references/api.md +4 -1
- package/templates/skills/bestax-custom-component/references/component-catalog.md +21 -3
- package/templates/skills/bestax-form/SKILL.md +19 -5
- package/templates/skills/bestax-form/references/api.md +3 -2
- package/templates/skills/bestax-icons/SKILL.md +3 -1
- package/templates/skills/bestax-layout-scaffold/SKILL.md +79 -5
- package/templates/skills/bestax-layout-scaffold/examples/app-shell.tsx +3 -1
- package/templates/skills/bestax-layout-scaffold/examples/card-grid.tsx +4 -3
- package/templates/skills/bestax-layout-scaffold/references/archetypes.md +13 -3
- package/templates/skills/bestax-layout-scaffold/references/layout-components.md +8 -6
- package/templates/skills/bestax-theming/SKILL.md +10 -8
- package/templates/skills/bestax-theming/examples/theme-config.tsx +2 -0
- package/templates/skills/bestax-theming/references/css-variables.md +4 -0
- package/templates/skills/bestax-theming/references/themeable-components.md +52 -40
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,SAAS,EAAE,QAAQ,EAG/B,CAAC;AAEF,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AACpD,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,kBAAkB,QAAsB,CAAC;AAEtD,eAAO,MAAM,QAAQ;;;;;;;wCAaQ,MAAM;uCAEP,MAAM;sCACP,MAAM;4CAEA,MAAM;;;;;CAM7B,CAAC;AASX,eAAO,MAAM,WAAW,QAed,CAAC;AAEX,eAAO,MAAM,OAAO;;;;;;CAOV,CAAC;AAOX,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAK9D,CAAC;AAKF,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,SAAS,GACpB,aAAa,MAAM,EACnB,8BAA8B,eAAe,KAC5C,
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,SAAS,EAAE,QAAQ,EAG/B,CAAC;AAEF,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AACpD,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,kBAAkB,QAAsB,CAAC;AAEtD,eAAO,MAAM,QAAQ;;;;;;;wCAaQ,MAAM;uCAEP,MAAM;sCACP,MAAM;4CAEA,MAAM;;;;;CAM7B,CAAC;AASX,eAAO,MAAM,WAAW,QAed,CAAC;AAEX,eAAO,MAAM,OAAO;;;;;;CAOV,CAAC;AAOX,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAK9D,CAAC;AAKF,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,SAAS,GACpB,aAAa,MAAM,EACnB,8BAA8B,eAAe,KAC5C,MAiHF,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IAIrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,cAAc,EAAE,WAAW,EA6CvC,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,eAAO,MAAM,aAAa,EAAE,WAAW,EA4CtC,CAAC"}
|
package/dist/constants.js
CHANGED
|
@@ -93,19 +93,56 @@ ${setupLines.join('\n')}
|
|
|
93
93
|
|
|
94
94
|
## House style
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
**Never inline \`style={{}}\`** — the components accept helper props that cover the common
|
|
97
|
+
cases. Before writing \`style\`, translate each declaration with this table:
|
|
98
|
+
|
|
99
|
+
| Inline style you're about to write | Helper props instead |
|
|
100
|
+
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
101
|
+
| \`marginTop: '1rem'\` (any margin/padding) | \`mt="4"\` — \`m\`/\`mt\`/\`mx\`/\`p\`/\`py\`/… scale: \`1\`=0.25rem, \`2\`=0.5rem, \`3\`=0.75rem, \`4\`=1rem, \`5\`=1.5rem, \`6\`=3rem (nearest step) |
|
|
102
|
+
| \`textAlign: 'center'\` | \`textAlign="centered"\` (also \`left\`, \`right\`, \`justified\`) |
|
|
103
|
+
| \`color: '#…'\` | \`textColor\` with the nearest Bulma color: \`primary\`, \`link\`, \`info\`, \`success\`, \`warning\`, \`danger\`, \`white\`, \`black\`, \`grey\` (+ \`grey-light\`, \`grey-dark\`, …) |
|
|
104
|
+
| \`backgroundColor: '#…'\` | \`bgColor\` (same palette) |
|
|
105
|
+
| \`fontSize: …\` | \`textSize="1"\`…\`"7"\` (\`1\` largest) — for headings use \`Title\`/\`SubTitle\` \`size\` |
|
|
106
|
+
| \`fontWeight: …\` | \`textWeight\`: \`light\`, \`normal\`, \`medium\`, \`semibold\`, \`bold\` |
|
|
107
|
+
| \`textTransform\`, italics | \`textTransform\`: \`uppercase\`, \`lowercase\`, \`capitalized\`, \`italic\` |
|
|
108
|
+
| \`display: 'flex'\` + flex properties | same-named props: \`display="flex"\`, \`flexDirection\`, \`justifyContent\`, \`alignItems\`, \`flexWrap\` |
|
|
109
|
+
| \`height: '100%'\` on a flex child | \`flexGrow="1"\` |
|
|
110
|
+
| \`display: 'none'\` | \`visibility="hidden"\`, or responsive \`display*\` props (\`displayMobile\`, \`displayTablet\`, …) |
|
|
111
|
+
|
|
112
|
+
- Spacing, typography, and flex helpers are on every component; \`textColor\`/\`bgColor\` are
|
|
113
|
+
on the content components (\`Box\`, \`Block\`, \`Title\`, \`Content\`, \`Hero\`, \`Card\`, …) — the
|
|
114
|
+
ones with a semantic \`color\` variant (\`Tag\`, \`Tabs\`, \`Panel\`) take \`color\` instead.
|
|
115
|
+
\`Notification\` is the mixed case: it takes \`textColor\`, but its background comes from
|
|
116
|
+
the semantic \`color\` prop, not \`bgColor\`.
|
|
117
|
+
- Flex layouts have no \`gap\` helper — space children with margins (\`Grid\` and \`Columns\`
|
|
99
118
|
take a \`gap\` prop, so prefer that there).
|
|
119
|
+
- No helper matches (e.g. \`maxWidth\`, a one-off gradient)? Add a named class to
|
|
120
|
+
\`src/App.css\` and pass it via \`className\` — still never inline \`style\`.
|
|
121
|
+
- Don't hand-write Bulma utility classes either — bare text/markup has wrapper elements that
|
|
122
|
+
take the same helper props: \`Span\`, \`Paragraph\`, \`Strong\`, not \`<span className="has-text-…">\`.
|
|
123
|
+
The one exception: companion classes Bulma requires on \`<html>\`/\`<body>\` (e.g.
|
|
124
|
+
\`has-navbar-fixed-top\` with \`Navbar fixed="top"\`) are hand-added in \`index.html\` — no
|
|
125
|
+
component renders those elements.
|
|
126
|
+
- Compound sub-parts (\`Card.*\`, \`Modal.*\`, \`Tabs.*\`, \`Message.*\`) take \`className\` + HTML
|
|
127
|
+
attributes and their own few props — no Bulma helper props, no \`as\`/\`href\`: nest a
|
|
128
|
+
\`Link\`/\`Span\` inside instead. \`Tabs.Tab\` and \`Tabs.Content.Item\` each require \`index={i}\`,
|
|
129
|
+
and \`Tabs.Tab\` has built-in \`icon\`/\`disabled\` props — no nested \`Icon\` needed.
|
|
100
130
|
- Compose existing components before writing custom CSS; theme via \`Theme\` and \`--bulma-*\`
|
|
101
131
|
variables, never hardcoded colors.
|
|
102
132
|
- \`Navbar.Burger\`/\`Navbar.Menu\` are controlled — wire \`active\` via state on both, and pair
|
|
103
133
|
\`Navbar fixed="top"\` with the \`has-navbar-fixed-top\` class on \`<html>\` (never an inline
|
|
104
134
|
padding offset).
|
|
135
|
+
- Reusable components you write get the library's spine so helper props work on them too:
|
|
136
|
+
extend \`BulmaClassesProps\`, run your props through \`useBulmaClasses\`, merge the
|
|
137
|
+
\`bulmaHelperClasses\` it returns into \`className\`, and spread **its** \`rest\` (not the raw
|
|
138
|
+
props) — the bestax-custom-component skill has the full template.
|
|
105
139
|
- There is no test runner or Storybook in this app — don't assume one.
|
|
106
140
|
- \`index.html\`'s \`<title>\` starts as the project name and \`README.md\` is stock template
|
|
107
141
|
boilerplate — once this app has a real identity, set the title (and any meta tags) to match
|
|
108
142
|
it and rewrite the README to describe *this* app, not the template.
|
|
143
|
+
- Before adding a dependency, match the package manager to the app's lockfile
|
|
144
|
+
(\`pnpm-lock.yaml\` → pnpm, \`package-lock.json\` → npm, \`yarn.lock\` → yarn) — a mismatched
|
|
145
|
+
install fails or forks the lockfile.
|
|
109
146
|
|
|
110
147
|
## AI skills
|
|
111
148
|
|
|
@@ -121,6 +158,7 @@ automatically when the task matches:
|
|
|
121
158
|
- **bestax-migrate** — migrate code off react-bulma-components (v4): run the codemod, resolve its TODOs.
|
|
122
159
|
|
|
123
160
|
Prefer the library's components and these skills over hand-written Bulma markup or custom CSS.
|
|
161
|
+
Read skill \`references/\` files with absolute paths — the shell's cwd is not stable between commands.
|
|
124
162
|
|
|
125
163
|
\`.claude/launch.json\` declares this app's dev server for Claude Code's browser preview
|
|
126
164
|
(\`npm run dev\` on port 5173, \`--strictPort\`) — start it from there rather than rediscovering
|
package/package.json
CHANGED
|
@@ -49,13 +49,23 @@ label — use that instead"_ or _"No `ProfileCard` exists; I'll build one compos
|
|
|
49
49
|
## Composition first
|
|
50
50
|
|
|
51
51
|
Build from existing components before writing any CSS: `Box`, `Card`, `Title`, `SubTitle`,
|
|
52
|
-
`Icon`, `Block`, `Content`, `Tag`, plus the Bulma helper props
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
`Icon`, `Block`, `Content`, `Tag`, plus the shared Bulma helper props (spacing, color,
|
|
53
|
+
typography, flexbox). Compound sub-parts are the exception — `Card.Content`, `Modal.Card`,
|
|
54
|
+
`Tabs.Tab`, `Message.Body` take **no Bulma helper props**, just `className` + HTML attributes
|
|
55
|
+
plus their own few (`Tabs.Tab` requires `index={i}` and has built-in `disabled` and
|
|
56
|
+
`icon`/`iconLibrary`/`iconVariant`/`iconSize`/`iconFeatures` — don't nest an `<Icon>` there) —
|
|
57
|
+
so put helper props on the parent or on an element inside them, never invent them there. Most "custom components" are a composition function — zero new styles.
|
|
58
|
+
See `examples/stat-card.tsx` for a complete worked example.
|
|
55
59
|
|
|
56
60
|
## The component spine
|
|
57
61
|
|
|
58
|
-
Same shape the library itself uses, with all imports from the package.
|
|
62
|
+
Same shape the library itself uses, with all imports from the package. Every reusable
|
|
63
|
+
component gets it — including pure compositions with zero CSS (a heading block, a labeled
|
|
64
|
+
wrapper): extend `BulmaClassesProps`, run your props through `useBulmaClasses`, merge its
|
|
65
|
+
`bulmaHelperClasses` into `className`, and spread the `rest` **it** returns — spreading the raw
|
|
66
|
+
props instead leaks helper props onto the DOM and emits none of their classes. The
|
|
67
|
+
`usePrefixedClassNames` root class is needed only when component-scoped CSS (or a variant
|
|
68
|
+
class) targets it — a zero-CSS composition may omit that call. File at
|
|
59
69
|
`src/components/MyComponent.tsx`:
|
|
60
70
|
|
|
61
71
|
```tsx
|
|
@@ -101,8 +111,32 @@ free. `references/api.md` documents the helpers.
|
|
|
101
111
|
## Styling ladder — use the lowest rung that works
|
|
102
112
|
|
|
103
113
|
**Rung 1 — helper props only (default).** House rules: never `style={{}}`. Layout with
|
|
104
|
-
`Block`/`Box` and `display="flex"`, `flexDirection`, `alignItems`, `justifyContent`.
|
|
105
|
-
**no `gap` helper** — space children with `m*`/`p*` margins instead
|
|
114
|
+
`Block`/`Box` and `display="flex"`, `flexDirection`, `alignItems`, `justifyContent`. Flex
|
|
115
|
+
layouts have **no `gap` helper** — space children with `m*`/`p*` margins instead (`Grid` and
|
|
116
|
+
`Columns` take a `gap` prop). Before writing `style={{ … }}` anywhere, translate each
|
|
117
|
+
declaration:
|
|
118
|
+
|
|
119
|
+
| Inline style you're about to write | Helper props instead |
|
|
120
|
+
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
121
|
+
| `marginTop: '1rem'` (any margin/padding) | `mt="4"` — `m`/`mt`/`mx`/`p`/`py`/… scale: `1`=0.25rem, `2`=0.5rem, `3`=0.75rem, `4`=1rem, `5`=1.5rem, `6`=3rem (nearest step) |
|
|
122
|
+
| `textAlign: 'center'` | `textAlign="centered"` (also `left`, `right`, `justified`) |
|
|
123
|
+
| `color: '#…'` | `textColor` with the nearest Bulma color: `primary`, `link`, `info`, `success`, `warning`, `danger`, `white`, `black`, `grey` (+ `grey-light`, `grey-dark`, …) |
|
|
124
|
+
| `backgroundColor: '#…'` | `bgColor` (same palette) |
|
|
125
|
+
| `fontSize: …` | `textSize="1"`…`"7"` (`1` largest) — for headings use `Title`/`SubTitle` `size` |
|
|
126
|
+
| `fontWeight: …` | `textWeight`: `light`, `normal`, `medium`, `semibold`, `bold` |
|
|
127
|
+
| `textTransform`, italics | `textTransform`: `uppercase`, `lowercase`, `capitalized`, `italic` |
|
|
128
|
+
| `display: 'flex'` + flex properties | same-named props: `display="flex"`, `flexDirection`, `justifyContent`, `alignItems`, `flexWrap` |
|
|
129
|
+
| `height: '100%'` on a flex child | `flexGrow="1"` |
|
|
130
|
+
| `display: 'none'` | `visibility="hidden"`, or responsive `display*` props (`displayMobile`, `displayTablet`, …) |
|
|
131
|
+
|
|
132
|
+
Spacing, typography, and flex helpers are on every component; `textColor`/`bgColor` are on
|
|
133
|
+
the content components you'll compose with (`Box`, `Block`, `Title`, `Content`, `Card`, …) —
|
|
134
|
+
the ones with a semantic `color` variant (`Tag`, `Tabs`, `Panel`) take `color` instead.
|
|
135
|
+
`Notification` is the mixed case: it takes `textColor`, but its background comes from the
|
|
136
|
+
semantic `color` prop, not `bgColor`.
|
|
137
|
+
|
|
138
|
+
A value with no helper equivalent (`maxWidth: 720`, a one-off gradient) moves you to rung 2 —
|
|
139
|
+
a named class in a stylesheet — never to inline `style`.
|
|
106
140
|
|
|
107
141
|
**Rung 2 — a plain CSS file**, scoped under the component's class, consuming `--bulma-*`
|
|
108
142
|
variables — never literal colors, so `Theme` and dark mode keep working:
|
|
@@ -139,7 +173,9 @@ bestax-bulma. Then the full `register-vars`/`getVar` pattern from
|
|
|
139
173
|
Types don't see layout. Run `npm run dev`, render the component, and actually look at it:
|
|
140
174
|
vertical centering of inline text (use `display="flex" alignItems="center"`, not line-height
|
|
141
175
|
hacks), balanced padding, nothing clipping, every color/size variant, and **dark mode**
|
|
142
|
-
legibility. Fix what you see, then re-check.
|
|
176
|
+
legibility. Fix what you see, then re-check. No browser available (headless)? Fall back to
|
|
177
|
+
`npm run build` plus a Node `renderToString` smoke render, grep the emitted HTML for the
|
|
178
|
+
expected classes, and flag the visual pass as not done.
|
|
143
179
|
|
|
144
180
|
## Tests and stories in an app
|
|
145
181
|
|
|
@@ -152,8 +188,9 @@ render, prop→class mapping, helper-prop passthrough (`m="3"` → `m-3`), and t
|
|
|
152
188
|
|
|
153
189
|
- [ ] Inventory checked (catalog + bestax.io/docs/api) and the decision surfaced to the user.
|
|
154
190
|
- [ ] All imports from `@allxsmith/bestax-bulma` (no deep/internal paths).
|
|
155
|
-
- [ ] Composition first — existing components + helper props before any CSS.
|
|
156
|
-
- [ ] No inline `style={{}}` anywhere
|
|
191
|
+
- [ ] Composition first — existing components + helper props before any CSS; every reusable component gets the spine.
|
|
192
|
+
- [ ] No inline `style={{}}` anywhere — translate via the rung-1 mapping table; values with
|
|
193
|
+
no helper equivalent get a named class (rung 2).
|
|
157
194
|
- [ ] Lowest sufficient ladder rung (helper props → scoped CSS vars → Sass).
|
|
158
195
|
- [ ] All colors/radii derived from `--bulma-*` variables — no literals.
|
|
159
196
|
- [ ] Renders correctly via `npm run dev`, including dark mode.
|
|
@@ -64,7 +64,11 @@ export function StatCard({
|
|
|
64
64
|
size="large"
|
|
65
65
|
textColor={color}
|
|
66
66
|
mr="4"
|
|
67
|
-
|
|
67
|
+
// Decorative: the label below already says it, so hide it from AT —
|
|
68
|
+
// Icon otherwise emits its default aria-label="icon". (To *label* an
|
|
69
|
+
// icon, use Icon's own camelCase `ariaLabel`; most components take
|
|
70
|
+
// the plain aria-label attribute.)
|
|
71
|
+
aria-hidden="true"
|
|
68
72
|
/>
|
|
69
73
|
)}
|
|
70
74
|
{/* No `gap` helper exists — space siblings with margin props (mr above). */}
|
|
@@ -30,7 +30,10 @@ that can also be used on their own:
|
|
|
30
30
|
| Other | `useOtherClasses` | `float`, `overflow`, `radius`, `shadow`, `interaction`, `cursor`, `skeleton`, `clearfix`, `relative`, `fullHeight`, `responsive` |
|
|
31
31
|
|
|
32
32
|
Because the component destructures these into `bulmaHelperClasses`, callers get the full Bulma
|
|
33
|
-
helper surface for free on every component, and `rest` stays clean for DOM
|
|
33
|
+
helper surface for free on every component built this way, and `rest` stays clean for DOM
|
|
34
|
+
spreading. (Library compound sub-parts — `Card.Content`, `Modal.Card`, `Tabs.Tab`,
|
|
35
|
+
`Message.Body` — do **not** take helper props: just `className`, HTML attributes, and their own
|
|
36
|
+
few, e.g. `Tabs.Tab`'s required `index` and its built-in `icon`/`disabled` props.)
|
|
34
37
|
|
|
35
38
|
## `classNames(...)` and friends — `helpers/classNames.ts`
|
|
36
39
|
|
|
@@ -10,12 +10,30 @@ instead of hand-writing markup.
|
|
|
10
10
|
|
|
11
11
|
- **Full props are not listed here** (that would be too large to keep in context).
|
|
12
12
|
Follow a component's link for its complete prop table, or see the per-skill
|
|
13
|
-
references.
|
|
13
|
+
references. Value unions (`size`, `color`, variants) differ per component —
|
|
14
|
+
never reuse one by analogy (`Tag size` is `normal|medium|large`; `Button`
|
|
15
|
+
adds `small`): the bestax-theming skill's
|
|
16
|
+
`references/themeable-components.md` lists them verbatim. The installed
|
|
17
|
+
types are at `node_modules/@allxsmith/bestax-bulma/dist/types/` (the
|
|
18
|
+
symlink resolves under pnpm's isolated linker — go straight there, no
|
|
19
|
+
`find` hunt), and when a `.d.ts` shows an opaque alias (`size?: TagSize`),
|
|
20
|
+
grep the alias name in that same file for the literals instead of guessing.
|
|
21
|
+
Top-level components accept the shared Bulma **helper props**
|
|
14
22
|
(`m`/`p` spacing, `textColor`/`bgColor`, `textAlign`, `display`, flex, …) —
|
|
15
|
-
documented once in `references/api.md
|
|
23
|
+
documented once in `references/api.md` — with rare exceptions (`Skeleton`).
|
|
16
24
|
- **Compound components** expose sub-parts via dot access (e.g. `Card.Header`,
|
|
17
25
|
`Navbar.Item`, `Tabs.Tab`, `Hero.Body`, `Columns.Column`, `Table.Tr`); see the
|
|
18
|
-
component's linked page for the full set.
|
|
26
|
+
component's linked page for the full set. Sub-parts do **not** all take helper
|
|
27
|
+
props: the `Table.*`, `Menu.*`, and `Hero.*` families do (most `Navbar.*`
|
|
28
|
+
too), but `Card.*`, `Modal.*`, `Tabs.*`, and `Message.*` sub-parts take none —
|
|
29
|
+
just `className`, HTML attributes, and their own few (`Tabs.Tab` requires
|
|
30
|
+
`index` and has built-in `icon`/`disabled` props). Put helper props on the
|
|
31
|
+
parent or on an element inside (`Span`, `Paragraph`, …) instead.
|
|
32
|
+
- **Composing these into your own reusable component?** Use the spine in this
|
|
33
|
+
skill's `SKILL.md`: extend `BulmaClassesProps`, run your props through
|
|
34
|
+
`useBulmaClasses`, merge its `bulmaHelperClasses` into `className`, and spread
|
|
35
|
+
the `rest` it returns — so it takes the same helper props as the library
|
|
36
|
+
components.
|
|
19
37
|
- Raw `*Base` form exports (`InputBase`, `SelectBase`, `TextAreaBase`, …) are
|
|
20
38
|
escape-hatch variants of the convenience wrappers above them; see the Form docs.
|
|
21
39
|
|
|
@@ -11,8 +11,11 @@ This skill covers the form components in `@allxsmith/bestax-bulma` and how to co
|
|
|
11
11
|
**Important:** bestax-bulma ships **no form/validation library** — there is no integration with
|
|
12
12
|
formik, react-hook-form, yup, or zod, and no `useForm`-style hook. You own your form state with
|
|
13
13
|
plain React (`useState` / `useReducer` or any library you choose) and feed validation results
|
|
14
|
-
back
|
|
15
|
-
|
|
14
|
+
back via each input's own `color`, `message`, and `messageColor` props on the convenience inputs
|
|
15
|
+
(`Input`, `Select`, `TextArea`, …). Always put validation state on the **input**: `Field` has no
|
|
16
|
+
`message`/`messageColor`, and although `FieldProps` types a `color`, `Field` discards it — it
|
|
17
|
+
renders no class, so setting it looks right and does nothing. (`FieldLabel`/`FieldBody` do honor
|
|
18
|
+
`color`, as the `has-text-*` helper.) See **Validation without a library** below.
|
|
16
19
|
|
|
17
20
|
## Use when
|
|
18
21
|
|
|
@@ -113,6 +116,13 @@ Across the convenience inputs (`Input`, `Select`, `TextArea`, and similar):
|
|
|
113
116
|
Plus the full Bulma **helper props** (`m`, `p`, `textColor`, `display`, …) on every component
|
|
114
117
|
via `useBulmaClasses`.
|
|
115
118
|
|
|
119
|
+
⚠️ Full-width casing is inconsistent across the library: `Select`, `File`, and `Table` take
|
|
120
|
+
`isFullwidth` (lowercase w); `Button` alone takes `isFullWidth`; `Tabs` takes bare `fullwidth`.
|
|
121
|
+
|
|
122
|
+
⚠️ The `label` prop renders the `<label>` but does **not** wire `htmlFor`/`id` — assistive tech
|
|
123
|
+
gets no association. Pass `id` on the input plus `labelProps={{ htmlFor: sameId }}` (every
|
|
124
|
+
convenience input and `Field` accept `labelProps`).
|
|
125
|
+
|
|
116
126
|
## Convenience vs composed
|
|
117
127
|
|
|
118
128
|
- **Convenience** (`<Input label message … />`) — for typical, single-control fields. Fewer
|
|
@@ -195,15 +205,19 @@ Before calling a form done, **render it and look at it**: run `pnpm storybook` (
|
|
|
195
205
|
or the docs dev server, open the form, and check field alignment/spacing, the help-text/error
|
|
196
206
|
states, and the validation flow (submit empty → fields turn `danger` with messages; fix → errors
|
|
197
207
|
clear). If claude-in-chrome or Playwright is available, drive the browser and screenshot the
|
|
198
|
-
valid and error states; otherwise eyeball it yourself.
|
|
208
|
+
valid and error states; otherwise eyeball it yourself. No browser at all (headless CI)? Fall
|
|
209
|
+
back to a production build plus a Node `renderToString` smoke render, grep the emitted HTML
|
|
210
|
+
for the expected classes/states, and say plainly that the visual pass is still owed.
|
|
199
211
|
|
|
200
212
|
## Checklist
|
|
201
213
|
|
|
202
214
|
- [ ] Built from the shipped form components (no hand-rolled inputs / reinvented controls).
|
|
203
|
-
- [ ] Every
|
|
215
|
+
- [ ] Every label is programmatically associated: `label` prop + `id` on the input +
|
|
216
|
+
`labelProps={{ htmlFor }}`, or a `<label htmlFor>` when composing.
|
|
204
217
|
- [ ] Controlled inputs have both `value` and `onChange` (or use `defaultValue` uncontrolled).
|
|
205
218
|
- [ ] Error state shows via `color="danger"` + `message` + `messageColor="danger"`.
|
|
206
219
|
- [ ] Grouped/addon layouts use explicit `Field` + `Control` composition.
|
|
207
220
|
- [ ] No assumption of a built-in validation/form library — state is owned by the app.
|
|
208
221
|
- [ ] **Rendered and visually inspected in a browser** — layout and the error/validation states
|
|
209
|
-
look right, not just green tests.
|
|
222
|
+
look right, not just green tests. No browser available? The `renderToString` fallback above
|
|
223
|
+
counts only if you grepped the emitted classes/states **and** said the visual pass is owed.
|
|
@@ -16,7 +16,7 @@ Container and layout. Compound parts: `Field.Label`, `Field.Body`.
|
|
|
16
16
|
| `narrow` | `boolean` | Constrain to content width (inside horizontal bodies). |
|
|
17
17
|
| `label` | `ReactNode` | Convenience label. |
|
|
18
18
|
| `labelSize` | `'small' \| 'normal' \| 'medium' \| 'large'` | Label size. |
|
|
19
|
-
| `labelProps` | label attributes | Props for the `<label
|
|
19
|
+
| `labelProps` | label attributes | Props for the `<label>` — where `htmlFor` goes. |
|
|
20
20
|
| `textColor` / `bgColor` | Bulma color | Helper colors for the field. |
|
|
21
21
|
|
|
22
22
|
## Control — `form/Control.tsx`
|
|
@@ -59,7 +59,8 @@ Wraps a single input; adds icons and loading.
|
|
|
59
59
|
## Select / SelectBase, TextArea / TextAreaBase
|
|
60
60
|
|
|
61
61
|
Same convenience/raw split as Input. `Select` supports `isLoading` (on the control), `color`,
|
|
62
|
-
`size`, `isRounded`,
|
|
62
|
+
`size`, `isRounded`, `isFullwidth` (lowercase w — Button's is `isFullWidth`), `multiple` +
|
|
63
|
+
`multipleSize`, plus the Field/Control/message props. `TextArea` adds `rows` and
|
|
63
64
|
`hasFixedSize`.
|
|
64
65
|
|
|
65
66
|
## Checkbox / Checkboxes, Radio / Radios
|
|
@@ -61,7 +61,9 @@ don't rely on it.
|
|
|
61
61
|
|
|
62
62
|
## Accessibility
|
|
63
63
|
|
|
64
|
-
Every `Icon` renders `aria-label` (default `"icon"`).
|
|
64
|
+
Every `Icon` renders `aria-label` (default `"icon"`), set via its camelCase `ariaLabel` prop.
|
|
65
|
+
Only a few components declare that prop (`Icon`, `Delete`, `Slider`, `Carousel`) — everything
|
|
66
|
+
else takes the standard `aria-label` attribute, e.g. `<Navbar.Burger aria-label="menu" />`.
|
|
65
67
|
|
|
66
68
|
- **Meaningful icon** (stands alone, conveys information): pass a descriptive
|
|
67
69
|
`ariaLabel="Delete item"`.
|
|
@@ -45,16 +45,57 @@ Centered; a collection of items → Card grid. For mixed requests, pick the domi
|
|
|
45
45
|
`height: 100%` on the card doesn't help — the column's height is auto).
|
|
46
46
|
- Rely on Bulma's responsive defaults: `Columns` sit side by side on tablet and up and stack on
|
|
47
47
|
mobile. Add responsive `size*` props only to tune the breakpoints.
|
|
48
|
+
- Interactive extras don't share a state API — never transfer one by analogy:
|
|
49
|
+
`Collapse trigger={node} open/defaultOpen onOpen/onClose`, `Tabs value={i}/onChange`
|
|
50
|
+
(each `Tabs.Tab`/`Tabs.Content.Item` requires `index={i}`, and `Tabs.Content` must be a
|
|
51
|
+
**child of `<Tabs>`** — the active-tab context lives on it; a sibling panel never
|
|
52
|
+
switches), `Dropdown active/onActiveChange`,
|
|
53
|
+
`Steps value={i}/onStepClick items={[{label, icon?}]}` (child form is `Steps.Step`, not
|
|
54
|
+
`Steps.Item`). `Reveal cascade` staggers only its **direct children** — to stagger a grid,
|
|
55
|
+
put `<Reveal delay={i * 80}>` inside each `Cell`, not around the container.
|
|
56
|
+
- Link lists (footer nav, sidebars): a bare `UnorderedList` of `ListItem`s is already
|
|
57
|
+
marker-less and flush — Bulma's reset unstyles `ul` — so no prop or CSS is needed;
|
|
58
|
+
bullets appear only inside `Content`.
|
|
48
59
|
- `Navbar.Burger`/`Navbar.Menu` are **controlled** — wire the same `active` state to both:
|
|
49
60
|
`active` on `Navbar.Menu` shows/hides the mobile menu, while `active` + `onClick` on
|
|
50
61
|
`Navbar.Burger` make the burger toggle it and animate. Left unwired, clicking the burger
|
|
51
62
|
does nothing (no error, silent failure). For a `fixed="top"` `Navbar`, add the
|
|
52
63
|
`has-navbar-fixed-top` class to `<html>` so content is not hidden behind it — the library
|
|
53
64
|
does not do this automatically, and an inline padding offset is not a substitute.
|
|
54
|
-
- **Style with helper props
|
|
55
|
-
`
|
|
56
|
-
|
|
57
|
-
|
|
65
|
+
- **Style with helper props — no inline `style`, no raw Bulma `className`s.** Before writing
|
|
66
|
+
`style={{ … }}` anywhere, translate each declaration with the mapping table below — the helper
|
|
67
|
+
props cover the common cases. Bare markup has wrapper elements that take all
|
|
68
|
+
helper props: `<Span textSize="7" textColor="grey">`, `Paragraph`, `Strong` — never a raw
|
|
69
|
+
`<span className="is-size-7 has-text-grey">`. Table cells: `Th`/`Td` take `textAlign="right"`,
|
|
70
|
+
`textWeight`, `textSize` directly (their `color` prop colors the cell; for muted cell text
|
|
71
|
+
wrap content in `Span textColor="grey"`). Set the app-wide icon library once with
|
|
72
|
+
`<ConfigProvider iconLibrary="…">` at the root rather than `library` on every `<Icon>`.
|
|
73
|
+
- **Decorative CSS is budgeted: two compact rules, ≤10 lines per app — comments count:
|
|
74
|
+
at most one short inline note, never a file-header comment block — every value derived
|
|
75
|
+
from `--bulma-*`.** A marketing page gets at most one hero wash + one alternating section
|
|
76
|
+
band, applied via `className` — no resets (Bulma ships one; body/list margins are already
|
|
77
|
+
zero) and no grid textures, masks, or multi-layer backdrops; the components carry the design:
|
|
78
|
+
|
|
79
|
+
```css
|
|
80
|
+
.hero-wash {
|
|
81
|
+
background-image: radial-gradient(
|
|
82
|
+
60rem 30rem at 20% -10%,
|
|
83
|
+
hsl(var(--bulma-primary-h) var(--bulma-primary-s) 50% / 0.2),
|
|
84
|
+
transparent 60%
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
.section-alt {
|
|
88
|
+
background: var(--bulma-scheme-main-bis); /* next band: -ter */
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
A highlighted/"featured" `Card`/`Box` needs **no third rule**: wrap that one element in
|
|
93
|
+
`<Theme bulmaVars={{ '--bulma-shadow': '0 0 0 2px var(--bulma-primary)' }}>`. Override the
|
|
94
|
+
**upstream token**, not the component's own var: `.card`/`.box` re-declare
|
|
95
|
+
`--bulma-card-shadow`/`--bulma-box-shadow` on their own selector, so setting those from an
|
|
96
|
+
ancestor never wins (same for `--bulma-box-radius`; `--bulma-card-radius` is a literal with
|
|
97
|
+
no ancestor route at all). The subtree stays theme- and dark-mode-aware.
|
|
98
|
+
|
|
58
99
|
- **CTAs on a colored hero must stay legible in both schemes.** On a fixed-color surface
|
|
59
100
|
(`Hero color="primary"`, a dark banner), use **filled** buttons — `color="light"` or
|
|
60
101
|
`color="primary" isInverted` — never a thin `isOutlined` secondary: a light outline + light
|
|
@@ -63,6 +104,33 @@ color }}`. Set the app-wide icon library once with `<ConfigProvider iconLibrary=
|
|
|
63
104
|
`<Theme isRoot colorMode="light">` — so a visitor's OS dark mode can't flip Bulma's text
|
|
64
105
|
colors out from under the fixed palette (details: the `bestax-theming` skill's contrast rules).
|
|
65
106
|
|
|
107
|
+
## Inline style → helper prop mapping
|
|
108
|
+
|
|
109
|
+
Look up the declaration you were about to inline. The spacing, typography, and flex helpers
|
|
110
|
+
below are on every component; `textColor`/`bgColor` are on the content components you'll
|
|
111
|
+
target (`Box`, `Block`, `Title`, `Content`, `Hero`, `Card`, …) — the ones with a semantic
|
|
112
|
+
`color` variant (`Tag`, `Tabs`, `Panel`) take `color` instead; wrap content in a `Block` if
|
|
113
|
+
you need a text color there. `Notification` is the mixed case: it takes `textColor`, but its
|
|
114
|
+
background comes from the semantic `color` prop, not `bgColor`.
|
|
115
|
+
|
|
116
|
+
| Inline style you're about to write | Helper props instead |
|
|
117
|
+
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
118
|
+
| `marginTop: '1rem'` (any margin/padding) | `mt="4"` — `m`/`mt`/`mx`/`p`/`py`/… scale: `1`=0.25rem, `2`=0.5rem, `3`=0.75rem, `4`=1rem, `5`=1.5rem, `6`=3rem (nearest step) |
|
|
119
|
+
| `textAlign: 'center'` | `textAlign="centered"` (also `left`, `right`, `justified`) |
|
|
120
|
+
| `color: '#…'` | `textColor` with the nearest Bulma color: `primary`, `link`, `info`, `success`, `warning`, `danger`, `white`, `black`, `grey` (+ `grey-light`, `grey-dark`, …) |
|
|
121
|
+
| `backgroundColor: '#…'` | `bgColor` (same palette) |
|
|
122
|
+
| `fontSize: …` | `textSize="1"`…`"7"` (`1` largest) — for headings use `Title`/`SubTitle` `size` |
|
|
123
|
+
| `fontWeight: …` | `textWeight`: `light`, `normal`, `medium`, `semibold`, `bold` |
|
|
124
|
+
| `textTransform`, italics | `textTransform`: `uppercase`, `lowercase`, `capitalized`, `italic` |
|
|
125
|
+
| `display: 'flex'` + flex properties | same-named props: `display="flex"`, `flexDirection`, `justifyContent`, `alignItems`, `flexWrap` |
|
|
126
|
+
| `height: '100%'` on a flex child | `flexGrow="1"` |
|
|
127
|
+
| `display: 'none'` | `visibility="hidden"`, or responsive `display*` props (`displayMobile`, `displayTablet`, …) |
|
|
128
|
+
| `gap: …` in a flex layout | no `gap` helper exists — space children with `m*` margins; `Grid` and `Columns` take a `gap` prop, so prefer those there |
|
|
129
|
+
|
|
130
|
+
No helper matches (e.g. `maxWidth`, a one-off gradient)? Add a named class to the project
|
|
131
|
+
stylesheet (`src/App.css` in a scaffolded app) and pass it via `className` — still never
|
|
132
|
+
inline `style`.
|
|
133
|
+
|
|
66
134
|
## References
|
|
67
135
|
|
|
68
136
|
- `references/layout-components.md` — the layout component inventory: real prop names, types, and
|
|
@@ -89,5 +157,11 @@ color }}`. Set the app-wide icon library once with `<ConfigProvider iconLibrary=
|
|
|
89
157
|
- [ ] Wire `active` state to **both** `Navbar.Burger` and `Navbar.Menu` (they are controlled).
|
|
90
158
|
- [ ] For a fixed navbar, add `has-navbar-fixed-top` to `<html>`.
|
|
91
159
|
- [ ] Do not use `Tile` — it is not shipped.
|
|
92
|
-
- [ ] Style with helper props
|
|
160
|
+
- [ ] Style with helper props, not inline `style` — translate via the mapping table; values
|
|
161
|
+
with no helper get a named class in the stylesheet, never `style={{}}`. No raw Bulma
|
|
162
|
+
`className`s either (`Span`/`Paragraph` wrap bare text; `Th`/`Td` take `textAlign`/`textWeight`).
|
|
163
|
+
- [ ] Decorative CSS ≤10 lines total incl. comments — no file-header comment (hero wash + section band), `--bulma-*`-derived;
|
|
164
|
+
no resets — Bulma ships one. A featured-card ring is a scoped `<Theme bulmaVars>`, not CSS.
|
|
93
165
|
- [ ] Set the icon library once via `<ConfigProvider iconLibrary="…">` at the root.
|
|
166
|
+
- [ ] Site built? ~800 KB raw / ~82 KB gzip CSS is the expected default-flavor size — to shrink
|
|
167
|
+
it, run the `bestax-optimize` skill (measure first).
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
//
|
|
4
4
|
// A fixed-top navbar needs the `has-navbar-fixed-top` class on <html> so the page
|
|
5
5
|
// is padded below it — Bulma requires this and the library does NOT add it for
|
|
6
|
-
// you.
|
|
6
|
+
// you. In a real app set it statically in index.html; the useEffect below is the
|
|
7
|
+
// fallback for a conditionally-mounted navbar (and keeps this file self-contained).
|
|
8
|
+
// The columns sit side by side on tablet and up, and stack (menu above
|
|
7
9
|
// content) on mobile.
|
|
8
10
|
//
|
|
9
11
|
// `ConfigProvider` wraps the shell once at the root to set the app-wide icon
|
|
@@ -90,9 +90,10 @@ export default function CatalogPage() {
|
|
|
90
90
|
image={product.image}
|
|
91
91
|
imageAlt={product.name}
|
|
92
92
|
header={product.name}
|
|
93
|
-
footer
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
// Card wraps each footer item in .card-footer-item itself —
|
|
94
|
+
// no raw span/className needed (and a literal class would
|
|
95
|
+
// break under ConfigProvider classPrefix).
|
|
96
|
+
footer={product.price}
|
|
96
97
|
>
|
|
97
98
|
<p>{product.blurb}</p>
|
|
98
99
|
</Card>
|
|
@@ -22,6 +22,7 @@ app".
|
|
|
22
22
|
const [open, setOpen] = useState(false); // controls the burger + mobile menu
|
|
23
23
|
|
|
24
24
|
<>
|
|
25
|
+
{/* fixed="top" requires <html class="has-navbar-fixed-top"> — set it in index.html */}
|
|
25
26
|
<Navbar fixed="top" color="dark">
|
|
26
27
|
<Navbar.Brand>
|
|
27
28
|
<Navbar.Item href="#">Brand</Navbar.Item>
|
|
@@ -59,8 +60,9 @@ const [open, setOpen] = useState(false); // controls the burger + mobile menu
|
|
|
59
60
|
</>;
|
|
60
61
|
```
|
|
61
62
|
|
|
62
|
-
**Required:** add `has-navbar-fixed-top` to `<html>`
|
|
63
|
-
|
|
63
|
+
**Required:** add `has-navbar-fixed-top` to `<html>` — statically in `index.html`; an effect
|
|
64
|
+
(`examples/app-shell.tsx`) only for a conditionally-mounted navbar — so content is not hidden
|
|
65
|
+
behind the fixed navbar.
|
|
64
66
|
|
|
65
67
|
**Responsive:** the navbar collapses to a burger on mobile (`Navbar.Burger` + `Navbar.Menu active`).
|
|
66
68
|
The sidebar and content columns sit side by side on tablet and up, and stack (menu above content)
|
|
@@ -148,6 +150,14 @@ the fixed navbar — never an inline padding offset.
|
|
|
148
150
|
`Section`s already stack vertically. The feature `Columns` collapse to one feature
|
|
149
151
|
per row on mobile. Use `Hero size="large"` / `"fullheight"` for a taller hero.
|
|
150
152
|
|
|
153
|
+
**Site chrome:** a full site adds the App-shell `Navbar` (archetype 1, minus the sidebar) above
|
|
154
|
+
the `Hero`; if it's `fixed="top"`, the same `<html class="has-navbar-fixed-top">` requirement
|
|
155
|
+
applies here too.
|
|
156
|
+
|
|
157
|
+
**Alternating section bands:** tint every other `Section` with a scheme step —
|
|
158
|
+
`.section-alt { background: var(--bulma-scheme-main-bis); }` (next step `-ter`) — not
|
|
159
|
+
`bgColor="light"`/`"white"`: those are fixed colors that stay light when dark mode flips the text.
|
|
160
|
+
|
|
151
161
|
**Hero CTAs:** on a colored hero use **filled** buttons only — `color="light"` for the primary
|
|
152
162
|
CTA and `color="primary" isInverted` (solid white, primary text) for a secondary. A thin
|
|
153
163
|
`isOutlined` button on a fixed-color surface is low-contrast and degrades further under OS dark
|
|
@@ -204,7 +214,7 @@ search results, "a grid of cards".
|
|
|
204
214
|
flexGrow="1"
|
|
205
215
|
image={item.image}
|
|
206
216
|
header={item.name}
|
|
207
|
-
footer={
|
|
217
|
+
footer={item.price} // Card wraps footer items in .card-footer-item itself
|
|
208
218
|
>
|
|
209
219
|
{item.blurb}
|
|
210
220
|
</Card>
|
|
@@ -21,7 +21,8 @@ import {
|
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Every component also accepts the shared Bulma helper props (`m`/`p` spacing, `textAlign`,
|
|
24
|
-
`textColor`, `bgColor`, etc.).
|
|
24
|
+
`textColor`, `bgColor`, etc.). Flex helpers take the standard CSS values spelled in full:
|
|
25
|
+
`justifyContent="space-between"`, `alignItems="center"`.
|
|
25
26
|
|
|
26
27
|
> **Use helper props, never inline `style`, for spacing / alignment / color.** `mt="4"` (= 1rem)
|
|
27
28
|
> not `style={{ marginTop: '1rem' }}`; `textAlign="centered"` not `style={{ textAlign: 'center' }}`;
|
|
@@ -129,6 +130,9 @@ type BulmaColumnSize =
|
|
|
129
130
|
| 'four-fifths';
|
|
130
131
|
```
|
|
131
132
|
|
|
133
|
+
Numeric sizes are **numbers** — `sizeDesktop={7}`, never `"7"`; only the fraction names are
|
|
134
|
+
strings. (`gap` is the exception that accepts number **or** string.)
|
|
135
|
+
|
|
132
136
|
> Columns **stack on mobile** by default and go side-by-side at the tablet breakpoint and up.
|
|
133
137
|
> Use the per-breakpoint `size*` props to control how many cells share a row at each width.
|
|
134
138
|
|
|
@@ -219,11 +223,9 @@ flexDirection="column"` + `flexGrow="1"` pattern applies to `Cell` + `Card`.
|
|
|
219
223
|
|
|
220
224
|
The burger/menu is controlled state — toggle `Navbar.Burger active`/`onClick` and pass the same
|
|
221
225
|
flag to `Navbar.Menu active`. **A `fixed="top"` navbar requires `has-navbar-fixed-top` on `<html>`**
|
|
222
|
-
(Bulma offsets the page from it); the library adds no helper
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
document.documentElement.classList.add('has-navbar-fixed-top');
|
|
226
|
-
```
|
|
226
|
+
(Bulma offsets the page from it); the library adds no helper. Set it statically —
|
|
227
|
+
`<html class="has-navbar-fixed-top">` in `index.html` — reserving a `classList.add` effect for
|
|
228
|
+
a navbar that mounts conditionally.
|
|
227
229
|
|
|
228
230
|
**Routing:** in a routed app, don't use `href="#"` — render items as the router's link
|
|
229
231
|
component. `Menu.Item as={Link} to="/x"` and `Navbar.Item as={Link} to="/x"` both compile
|
|
@@ -9,12 +9,6 @@ license: MIT
|
|
|
9
9
|
`@allxsmith/bestax-bulma` wraps Bulma 1.x, which is themed through `--bulma-*` CSS custom
|
|
10
10
|
properties. Theme an app by overriding the right variables — no component re-styling required.
|
|
11
11
|
|
|
12
|
-
## Use when
|
|
13
|
-
|
|
14
|
-
- Setting a brand/primary color or recoloring `link`/`info`/`success`/`warning`/`danger`.
|
|
15
|
-
- Adjusting global tokens — radius, fonts, sizes, weights.
|
|
16
|
-
- Adding light/dark mode.
|
|
17
|
-
|
|
18
12
|
## Approach
|
|
19
13
|
|
|
20
14
|
Recolor a brand color by overriding its **hue/saturation/lightness trio** — Bulma derives every
|
|
@@ -50,13 +44,21 @@ color tokens or fixed-color surfaces exist:
|
|
|
50
44
|
(`--my-canvas: var(--bulma-scheme-main)`) — or flip them yourself under **both** dark-mode
|
|
51
45
|
paths: `[data-theme='dark']` **and** `@media (prefers-color-scheme: dark)` scoped to
|
|
52
46
|
`:root:not([data-theme])`, since `colorMode="system"` removes the attribute (snippets in
|
|
53
|
-
`references/css-variables.md`).
|
|
47
|
+
`references/css-variables.md`). Alternating/tinted section bands are this case:
|
|
48
|
+
`background: var(--bulma-scheme-main-bis)` (then `-ter`), never `bgColor="light"` —
|
|
49
|
+
`light`/`white`/grey helper backgrounds are fixed colors that fight dark mode.
|
|
54
50
|
- **Fixed-color surface → fixed-color content.** On a surface that never changes (a dark hero,
|
|
55
51
|
a brand banner), pin the content's colors too: solid/filled buttons and explicit text colors,
|
|
56
52
|
never scheme-derived defaults or thin outlines that depend on the flipping scheme.
|
|
57
53
|
|
|
58
54
|
Reach for the helper props (`color` / `textColor` / `bgColor` / `colorShade`, `textSize`,
|
|
59
55
|
`textWeight`, `fontFamily`) to apply themed colors and type to individual components.
|
|
56
|
+
Variant flags and value unions are component-specific — never carry one over by analogy:
|
|
57
|
+
`isLight` exists on `Button` and `Notification` **only** (`Tag` has none, and `LinkButtonProps`
|
|
58
|
+
omits it);
|
|
59
|
+
`Tag size` is `normal | medium | large` (no `small`, unlike `Button`); `Buttons` has
|
|
60
|
+
`isCentered`, `Tags` does not (center tags with `justifyContent="center"`); the verbatim
|
|
61
|
+
truth table is `references/themeable-components.md`.
|
|
60
62
|
|
|
61
63
|
## Quick start
|
|
62
64
|
|
|
@@ -109,7 +111,7 @@ Nest `Theme` and `ConfigProvider` together at the root (order doesn't matter).
|
|
|
109
111
|
|
|
110
112
|
- [ ] Recolor brand colors via the HSL trio (`*-h` / `*-s` / `*-l`), not by hard-coding hex on components.
|
|
111
113
|
- [ ] Apply a global theme once with `<Theme isRoot>` (or `:root`); use scoped `<Theme>` for one-off sections.
|
|
112
|
-
- [ ] Set non-color tokens (radius, fonts, sizes) through `bulmaVars` or `:root
|
|
114
|
+
- [ ] 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).
|
|
113
115
|
- [ ] Implement dark mode with `data-theme` on `<html>`; do not expect a shipped dark-mode component.
|
|
114
116
|
- [ ] Pass `color`/`textColor`/`bgColor` (not custom CSS) to color individual components.
|
|
115
117
|
- [ ] Set the icon library once with `<ConfigProvider iconLibrary="…">` at the root, not `library` on every `<Icon>`.
|
|
@@ -34,6 +34,8 @@ export function ThemedApp({ children }: { children: React.ReactNode }) {
|
|
|
34
34
|
bulmaVars={{
|
|
35
35
|
'--bulma-radius': '0.75rem',
|
|
36
36
|
'--bulma-radius-large': '1.25rem',
|
|
37
|
+
// 'Inter' must actually be loaded (index.html <link> or an @fontsource
|
|
38
|
+
// import) — declaring the family var alone falls back to system-ui.
|
|
37
39
|
'--bulma-family-primary': "'Inter', system-ui, sans-serif",
|
|
38
40
|
}}
|
|
39
41
|
>
|
|
@@ -33,6 +33,10 @@ theming means overriding the right `--bulma-*` values.
|
|
|
33
33
|
> Note: the type of the `bulmaVars` keys is not exported — pass it as an object literal (TypeScript
|
|
34
34
|
> still checks the keys against the allowed `--bulma-*` names).
|
|
35
35
|
|
|
36
|
+
> `--bulma-family-*` only selects the family — also load the font itself (a `<link>` in
|
|
37
|
+
> `index.html` or an `@fontsource/*` package import), or the browser silently falls back to
|
|
38
|
+
> the system font.
|
|
39
|
+
|
|
36
40
|
### 2. Plain CSS
|
|
37
41
|
|
|
38
42
|
Set the variables yourself on any selector. `:root` themes the whole document; a class scopes it.
|
|
@@ -8,13 +8,25 @@ This is the self-contained inventory of the color/size/variant props that matter
|
|
|
8
8
|
|
|
9
9
|
1. **Component `color` modifier** → emits `is-<color>` (the filled Bulma variant). The accepted
|
|
10
10
|
values are component-specific (see the table). Example: `<Button color="primary">` → `is-primary`.
|
|
11
|
-
|
|
11
|
+
⚠️ Some unions are **typed wider than the CSS Bulma ships** — the class is emitted but no rule
|
|
12
|
+
matches. No component ships `is-grey*`/`is-*-bis`/`is-*-ter` rules at all: those `validColors`
|
|
13
|
+
members typecheck on `Progress`/`Notification`/`Hero` but style nothing (the `has-text-*`/
|
|
14
|
+
`has-background-*` **helpers** do cover all 17). Before relying on an unusual value, grep the
|
|
15
|
+
shipped CSS: `node_modules/@allxsmith/bestax-bulma/dist/bestax.css` for e.g. `.progress.is-grey`.
|
|
16
|
+
2. **Helper color props** (on most components, applied as utility classes):
|
|
12
17
|
- `color` / `textColor` → `has-text-<color>` (text color)
|
|
13
18
|
- `backgroundColor` / `bgColor` → `has-background-<color>` (background)
|
|
14
19
|
- `colorShade` / `backgroundColorShade` → adds a shade suffix, e.g. `has-text-primary-30`
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
**`
|
|
21
|
+
Components with a real `is-<color>` modifier (`Button`, `Hero`) drop the `color` helper and
|
|
22
|
+
re-expose it as **`textColor`** / **`bgColor`**. `Box`/`Card`/`Section` ship no `is-<color>`
|
|
23
|
+
rule — their `color` _is_ the text helper (`has-text-<color>`; narrowed to the 6 on
|
|
24
|
+
`Box`/`Card`), so `color` and `textColor` are the same lever there. `Tag` and `Td`/`Th` have
|
|
25
|
+
**no text-color prop** — wrap content in `<Span textColor="…">`. `Input` has none either and
|
|
26
|
+
the wrapper trick can't work (it renders a native `<input>`; a child can't color its value):
|
|
27
|
+
recolor via the upstream `--bulma-text-strong-l`, since Bulma re-declares `--bulma-input-*` on
|
|
28
|
+
`.input` itself and an ancestor `<Theme>` can't reach those. Raw `backgroundColor` still works
|
|
29
|
+
on `Tag` and `Input`.
|
|
18
30
|
|
|
19
31
|
`<color>` for the helper props is one of **`validColors`**:
|
|
20
32
|
|
|
@@ -28,43 +40,43 @@ Shades (`colorShade` / `backgroundColorShade`): `00, 05, 10, … 95, invert, lig
|
|
|
28
40
|
|
|
29
41
|
## Component `color` / `size` props (verbatim unions)
|
|
30
42
|
|
|
31
|
-
| Component | `color` accepts | `size` accepts | Notes
|
|
32
|
-
| ------------------ | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
|
|
33
|
-
| `Button` | `primary \| link \| info \| success \| warning \| danger \| white \| light \| dark \| black \| text \| ghost` | `small \| normal \| medium \| large` | adds `text`, `ghost`; also `isLight`, `isOutlined`, `isInverted`, `isRounded`
|
|
34
|
-
| `Notification` | the 17 `validColors`
|
|
35
|
-
| `Tag` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `normal \| medium \| large` | also `isRounded`, `isDelete`, `isHoverable` |
|
|
36
|
-
| `Box` | `primary \| link \| info \| success \| warning \| danger` | — |
|
|
37
|
-
| `Message` | `primary \| link \| info \| success \| warning \| danger` | — | the 6 only
|
|
38
|
-
| `Input` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | also `isRounded`, `isStatic`
|
|
39
|
-
| `Avatar` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `16x16 \| 24x24 \| 32x32 \| 48x48 \| 64x64 \| 96x96 \| 128x128 \| number` | initials/icon background (auto-derived from `name` when unset); also `shape`
|
|
40
|
-
| `Badge` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | — | pill background; default `danger`
|
|
41
|
-
| `Title` | — (no `color`; use `textColor`) | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | also `isSpaced`
|
|
42
|
-
| `SubTitle` | — (no `color`; use `textColor`) | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | —
|
|
43
|
-
| `Autocomplete` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | the 6 only
|
|
44
|
-
| `Checkbox` | `primary \| link \| info \| success \| warning \| danger` | `small \| normal \| medium \| large` | the 6 only
|
|
45
|
-
| `DateInput` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | also `isRounded`
|
|
46
|
-
| `DateTimeInput` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | also `isRounded`
|
|
47
|
-
| `File` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | also `isBoxed`, `isFullwidth`
|
|
48
|
-
| `Hero` | the 17 `validColors`
|
|
49
|
-
| `LinkButton` | `primary \| link \| info \| success \| warning \| danger \| white \| light \| dark \| black` | — | button-styled link; emits `link-button-<color>`
|
|
50
|
-
| `Loading` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | spinner color; default light grey
|
|
51
|
-
| `Navbar` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | — | —
|
|
52
|
-
| `Numberinput` | `primary \| link \| info \| success \| warning \| danger \| light \| dark` | `small \| medium \| large` | also `inputColor` (the 6) for the inner input
|
|
53
|
-
| `Pagination` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | —
|
|
54
|
-
| `Panel` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | — | —
|
|
55
|
-
| `Progress` | the 17 `validColors`
|
|
56
|
-
| `Radio` | `primary \| link \| info \| success \| warning \| danger` | `small \| normal \| medium \| large` | the 6 only
|
|
57
|
-
| `Rate` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | the 6 only
|
|
58
|
-
| `Select` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | also `isRounded`
|
|
59
|
-
| `Slider` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | also `isRounded`, `isCircle`
|
|
60
|
-
| `Steps` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | the 6 only
|
|
61
|
-
| `Switch` | `primary \| link \| info \| success \| warning \| danger` | `small \| normal \| medium \| large` | also `isRounded`, `isThin`, `isOutlined`
|
|
62
|
-
| `Tabs` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | —
|
|
63
|
-
| `Taginput` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | also `tagColor` (the 6 + `dark \| light`) for the tags
|
|
64
|
-
| `TextArea` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | also `isRounded`, `isStatic`
|
|
65
|
-
| `TimeInput` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | —
|
|
66
|
-
| `Tooltip` | `primary \| link \| info \| success \| warning \| danger \| dark \| light` | `small \| medium \| large` | —
|
|
67
|
-
| `Tr` / `Td` / `Th` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | — |
|
|
43
|
+
| Component | `color` accepts | `size` accepts | Notes |
|
|
44
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
45
|
+
| `Button` | `primary \| link \| info \| success \| warning \| danger \| white \| light \| dark \| black \| text \| ghost` | `small \| normal \| medium \| large` | adds `text`, `ghost`; also `isLight`, `isOutlined`, `isInverted`, `isRounded` |
|
|
46
|
+
| `Notification` | the 17 `validColors` (greys typecheck, no CSS — see ⚠️) | — | also `isLight` |
|
|
47
|
+
| `Tag` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `normal \| medium \| large` | also `isRounded`, `isDelete`, `isHoverable` — **no `isLight`** |
|
|
48
|
+
| `Box` | `primary \| link \| info \| success \| warning \| danger` | — | `color` renders `has-text-<color>` (no `.box.is-*` ships — tint via `bgColor`); also `hasShadow` |
|
|
49
|
+
| `Message` | `primary \| link \| info \| success \| warning \| danger` | — | the 6 only |
|
|
50
|
+
| `Input` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | also `isRounded`, `isStatic` |
|
|
51
|
+
| `Avatar` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `16x16 \| 24x24 \| 32x32 \| 48x48 \| 64x64 \| 96x96 \| 128x128 \| number` | initials/icon background (auto-derived from `name` when unset); also `shape` |
|
|
52
|
+
| `Badge` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | — | pill background; default `danger` |
|
|
53
|
+
| `Title` | — (no `color`; use `textColor`) | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | also `isSpaced` |
|
|
54
|
+
| `SubTitle` | — (no `color`; use `textColor`) | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | — |
|
|
55
|
+
| `Autocomplete` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | the 6 only |
|
|
56
|
+
| `Checkbox` | `primary \| link \| info \| success \| warning \| danger` | `small \| normal \| medium \| large` | the 6 only |
|
|
57
|
+
| `DateInput` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | also `isRounded` |
|
|
58
|
+
| `DateTimeInput` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | also `isRounded` |
|
|
59
|
+
| `File` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | also `isBoxed`, `isFullwidth` |
|
|
60
|
+
| `Hero` | the 17 `validColors` (greys typecheck, no CSS — see ⚠️) | `small \| medium \| large \| fullheight \| fullheight-with-navbar` | section background |
|
|
61
|
+
| `LinkButton` | `primary \| link \| info \| success \| warning \| danger \| white \| light \| dark \| black` | — | button-styled link; emits `link-button-<color>` — **no `isLight`/`isOutlined`/`isInverted`** |
|
|
62
|
+
| `Loading` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | spinner color; default light grey |
|
|
63
|
+
| `Navbar` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | — | — |
|
|
64
|
+
| `Numberinput` | `primary \| link \| info \| success \| warning \| danger \| light \| dark` | `small \| medium \| large` | also `inputColor` (the 6) for the inner input |
|
|
65
|
+
| `Pagination` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | — |
|
|
66
|
+
| `Panel` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | — | — |
|
|
67
|
+
| `Progress` | the 17 `validColors` (greys typecheck, no CSS — see ⚠️) | `small \| medium \| large` | — |
|
|
68
|
+
| `Radio` | `primary \| link \| info \| success \| warning \| danger` | `small \| normal \| medium \| large` | the 6 only |
|
|
69
|
+
| `Rate` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | the 6 only |
|
|
70
|
+
| `Select` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | also `isRounded` |
|
|
71
|
+
| `Slider` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | also `isRounded`, `isCircle` |
|
|
72
|
+
| `Steps` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | the 6 only |
|
|
73
|
+
| `Switch` | `primary \| link \| info \| success \| warning \| danger` | `small \| normal \| medium \| large` | also `isRounded`, `isThin`, `isOutlined` |
|
|
74
|
+
| `Tabs` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | — |
|
|
75
|
+
| `Taginput` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | also `tagColor` (the 6 + `dark \| light`) for the tags |
|
|
76
|
+
| `TextArea` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | `small \| medium \| large` | also `isRounded`, `isStatic` |
|
|
77
|
+
| `TimeInput` | `primary \| link \| info \| success \| warning \| danger` | `small \| medium \| large` | — |
|
|
78
|
+
| `Tooltip` | `primary \| link \| info \| success \| warning \| danger \| dark \| light` | `small \| medium \| large` | — |
|
|
79
|
+
| `Tr` / `Td` / `Th` | `primary \| link \| info \| success \| warning \| danger \| black \| dark \| light \| white` | — | cell background; cells take `textAlign`/`textWeight`/`textSize` directly |
|
|
68
80
|
|
|
69
81
|
The 6 brand colors (`primary, link, info, success, warning, danger`) are the ones a custom theme
|
|
70
82
|
recolors via the HSL trios (see `css-variables.md`). The greyscale and `white`/`light`/`dark`
|