create-bestax 4.2.14 → 4.2.16
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 +9 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +102 -31
- package/dist/project-creator.d.ts +19 -0
- package/dist/project-creator.d.ts.map +1 -1
- package/dist/project-creator.js +91 -38
- package/package.json +2 -2
- package/templates/skills/bestax-form/SKILL.md +19 -8
- package/templates/skills/bestax-form/references/api.md +10 -10
- package/templates/skills/bestax-form/references/patterns.md +22 -3
- package/templates/skills/bestax-icons/references/icon-libraries.md +9 -4
- package/templates/skills/bestax-migrate/references/bulma-classes/component-map.md +8 -0
- package/templates/skills/bestax-migrate/references/bulma-classes/unmappables.md +5 -0
- package/templates/skills/bestax-optimize/references/modular-build.md +9 -7
- package/templates/vite/README.md +5 -6
- package/templates/vite/src/App.css +14 -1
- package/templates/vite/src/App.jsx +128 -100
- package/templates/vite/src/main.jsx +3 -2
- package/templates/vite-ts/README.md +5 -6
- package/templates/vite-ts/src/App.css +14 -1
- package/templates/vite-ts/src/App.tsx +128 -100
- package/templates/vite-ts/src/main.tsx +3 -2
- package/templates/vite-ts/tsconfig.app.json +25 -0
- package/templates/vite-ts/tsconfig.json +6 -25
- package/templates/vite-ts/tsconfig.node.json +2 -2
package/dist/constants.d.ts
CHANGED
|
@@ -66,6 +66,15 @@ export interface BulmaFlavor {
|
|
|
66
66
|
color: typeof chalk.yellow;
|
|
67
67
|
importStatement: string;
|
|
68
68
|
needsPrefix?: boolean;
|
|
69
|
+
noHelpers?: boolean;
|
|
69
70
|
}
|
|
70
71
|
export declare const BULMA_FLAVORS: BulmaFlavor[];
|
|
72
|
+
export declare const CSS_ORDER_COMMENT: string;
|
|
73
|
+
export interface StarterClass {
|
|
74
|
+
prop: string;
|
|
75
|
+
className: string;
|
|
76
|
+
declaration: string;
|
|
77
|
+
}
|
|
78
|
+
export declare const NO_HELPERS_STARTER_CLASSES: StarterClass[];
|
|
79
|
+
export declare const NO_HELPERS_APP_CSS: string;
|
|
71
80
|
//# sourceMappingURL=constants.d.ts.map
|
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;AAGtD,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAOpC,eAAO,MAAM,QAAQ;;;;;;6CASa,MAAM;;;wCAQX,MAAM,WAAW,MAAM,EAAE;oCAE7B,MAAM;gDAIM,MAAM,WAAW,MAAM,EAAE;uCAIlC,MAAM;sCACP,MAAM;4CAEA,MAAM;;;;;wCAMV,MAAM,mBAAmB,MAAM;;;;;CAclD,CAAC;AASX,eAAO,MAAM,WAAW,QAed,CAAC;AAEX,eAAO,MAAM,OAAO;;;;;;;CAQV,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;
|
|
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;AAGtD,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAOpC,eAAO,MAAM,QAAQ;;;;;;6CASa,MAAM;;;wCAQX,MAAM,WAAW,MAAM,EAAE;oCAE7B,MAAM;gDAIM,MAAM,WAAW,MAAM,EAAE;uCAIlC,MAAM;sCACP,MAAM;4CAEA,MAAM;;;;;wCAMV,MAAM,mBAAmB,MAAM;;;;;CAclD,CAAC;AASX,eAAO,MAAM,WAAW,QAed,CAAC;AAEX,eAAO,MAAM,OAAO;;;;;;;CAQV,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;AAkCD,eAAO,MAAM,SAAS,GACpB,aAAa,MAAM,EACnB,8BAA8B,eAAe,KAC5C,MA8HF,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;IAGzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,cAAc,EAAE,WAAW,EA8DvC,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;IAGtB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,eAAO,MAAM,aAAa,EAAE,WAAW,EA8CtC,CAAC;AAKF,eAAO,MAAM,iBAAiB,QAE+B,CAAC;AAE9D,MAAM,WAAW,YAAY;IAE3B,IAAI,EAAE,MAAM,CAAC;IAEb,SAAS,EAAE,MAAM,CAAC;IAElB,WAAW,EAAE,MAAM,CAAC;CACrB;AAOD,eAAO,MAAM,0BAA0B,EAAE,YAAY,EAgBpD,CAAC;AAEF,eAAO,MAAM,kBAAkB,QAMjB,CAAC"}
|
package/dist/constants.js
CHANGED
|
@@ -87,6 +87,36 @@ export const CONFIG_PROVIDER_ICON_VALUES = {
|
|
|
87
87
|
'material-icons': 'material-icons',
|
|
88
88
|
'material-symbols': 'material-symbols',
|
|
89
89
|
};
|
|
90
|
+
// The house style opens with the inline-style table for a flavor that has the
|
|
91
|
+
// helper classes, and with the named-class rule for one that does not.
|
|
92
|
+
const HELPER_HOUSE_STYLE = `**Never inline \`style={{}}\`** — the components accept helper props that cover the common
|
|
93
|
+
cases. Before writing \`style\`, translate each declaration with this table:
|
|
94
|
+
|
|
95
|
+
| Inline style you're about to write | Helper props instead |
|
|
96
|
+
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
97
|
+
| \`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) |
|
|
98
|
+
| \`textAlign: 'center'\` | \`textAlign="centered"\` (also \`left\`, \`right\`, \`justified\`) |
|
|
99
|
+
| \`color: '#…'\` | \`textColor\` with the nearest Bulma color: \`primary\`, \`link\`, \`info\`, \`success\`, \`warning\`, \`danger\`, \`white\`, \`black\`, \`grey\` (+ \`grey-light\`, \`grey-dark\`, …) |
|
|
100
|
+
| \`backgroundColor: '#…'\` | \`bgColor\` (same palette) |
|
|
101
|
+
| \`fontSize: …\` | \`textSize="1"\`…\`"7"\` (\`1\` largest) — for headings use \`Title\`/\`SubTitle\` \`size\` |
|
|
102
|
+
| \`fontWeight: …\` | \`textWeight\`: \`light\`, \`normal\`, \`medium\`, \`semibold\`, \`bold\` |
|
|
103
|
+
| \`textTransform\`, italics | \`textTransform\`: \`uppercase\`, \`lowercase\`, \`capitalized\`, \`italic\` |
|
|
104
|
+
| \`display: 'flex'\` + flex properties | same-named props: \`display="flex"\`, \`flexDirection\`, \`justifyContent\`, \`alignItems\`, \`flexWrap\` |
|
|
105
|
+
| \`height: '100%'\` on a flex child | \`flexGrow="1"\` |
|
|
106
|
+
| \`display: 'none'\` | \`visibility="hidden"\`, or responsive \`display*\` props (\`displayMobile\`, \`displayTablet\`, …) |
|
|
107
|
+
| \`gap: '1rem'\` (flex or grid) | \`gap="2"\`, or one axis: \`columnGap\`, \`rowGap\`; gap scale: \`1\`=0.5rem, \`2\`=1rem, \`4\`=2rem, half steps (\`"1.5"\`) |
|
|
108
|
+
|
|
109
|
+
- Spacing, typography, and flex helpers are on every component; \`textColor\`/\`bgColor\` are
|
|
110
|
+
on the content components (\`Box\`, \`Block\`, \`Title\`, \`Content\`, \`Hero\`, \`Card\`, …) — the
|
|
111
|
+
ones with a semantic \`color\` variant (\`Tag\`, \`Tabs\`, \`Panel\`) take \`color\` instead.
|
|
112
|
+
\`Notification\` is the mixed case: it takes \`textColor\`, but its background comes from
|
|
113
|
+
the semantic \`color\` prop, not \`bgColor\`.
|
|
114
|
+
- No helper matches (e.g. \`maxWidth\`, a one-off gradient)? Add a named class to
|
|
115
|
+
\`src/App.css\` and pass it via \`className\` — still never inline \`style\`.`;
|
|
116
|
+
const NO_HELPERS_HOUSE_STYLE = `**Never inline \`style={{}}\`**, and don't reach for helper props in its place either:
|
|
117
|
+
this app's flavor leaves out Bulma's helper classes, so \`mt="4"\`, \`textAlign="centered"\` and \`flexGrow="1"\`
|
|
118
|
+
render nothing. Write a named class in \`src/App.css\`, which loads after Bulma's CSS, and pass it via \`className\`.
|
|
119
|
+
`;
|
|
90
120
|
export const CLAUDE_MD = (projectName, { bulmaFlavor, iconLibrary }) => {
|
|
91
121
|
const flavor = BULMA_FLAVORS.find(f => f.name === bulmaFlavor);
|
|
92
122
|
const icon = ICON_LIBRARIES.find(lib => lib.name === iconLibrary);
|
|
@@ -99,6 +129,13 @@ export const CLAUDE_MD = (projectName, { bulmaFlavor, iconLibrary }) => {
|
|
|
99
129
|
setupLines.push(`- Every Bulma class carries the \`bestax-\` prefix and the app is wrapped in ` +
|
|
100
130
|
`\`<ConfigProvider classPrefix="bestax-">\` — custom CSS selectors must match the prefix.`);
|
|
101
131
|
}
|
|
132
|
+
if (flavor?.noHelpers) {
|
|
133
|
+
setupLines.push(`- This flavor leaves out Bulma's helper classes, so **helper props that add those classes render nothing**: ` +
|
|
134
|
+
`\`mt\`, \`gap\`, \`textAlign\`, \`textColor\`, \`display\` and \`flexGrow\` still add ` +
|
|
135
|
+
`class names, but no rule matches them. \`skeleton\` still works, because the flavor keeps ` +
|
|
136
|
+
`Bulma's skeleton styles. The starter page lays itself out with named classes in ` +
|
|
137
|
+
`\`src/App.css\` instead.`);
|
|
138
|
+
}
|
|
102
139
|
const providerIconValue = CONFIG_PROVIDER_ICON_VALUES[iconLibrary];
|
|
103
140
|
setupLines.push(iconLibrary === 'none'
|
|
104
141
|
? `- No icon library is installed — add one before using \`<Icon>\` ` +
|
|
@@ -107,7 +144,17 @@ export const CLAUDE_MD = (projectName, { bulmaFlavor, iconLibrary }) => {
|
|
|
107
144
|
(providerIconValue
|
|
108
145
|
? ` The app is already wrapped in \`<ConfigProvider iconLibrary="${providerIconValue}">\` ` +
|
|
109
146
|
`— extend that provider rather than adding a second one.`
|
|
110
|
-
: '')
|
|
147
|
+
: '') +
|
|
148
|
+
(icon?.setupInstructions ? ` ${icon.setupInstructions}` : ''));
|
|
149
|
+
const houseStyleOpening = flavor?.noHelpers
|
|
150
|
+
? NO_HELPERS_HOUSE_STYLE
|
|
151
|
+
: HELPER_HOUSE_STYLE;
|
|
152
|
+
// The wrapper elements are only a way out where their helper props render.
|
|
153
|
+
const utilityClassRule = flavor?.noHelpers
|
|
154
|
+
? `- Don't hand-write Bulma utility classes either: this flavor doesn't ship them, so a
|
|
155
|
+
\`has-text-…\` class renders nothing, and the named class above is what works.`
|
|
156
|
+
: `- Don't hand-write Bulma utility classes either — bare text/markup has wrapper elements that
|
|
157
|
+
take the same helper props: \`Span\`, \`Paragraph\`, \`Strong\`, not \`<span className="has-text-…">\`.`;
|
|
111
158
|
return `# ${projectName}
|
|
112
159
|
|
|
113
160
|
This app is built with [\`@allxsmith/bestax-bulma\`](https://bestax.io) — React components for
|
|
@@ -119,36 +166,12 @@ ${setupLines.join('\n')}
|
|
|
119
166
|
|
|
120
167
|
## House style
|
|
121
168
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
| Inline style you're about to write | Helper props instead |
|
|
126
|
-
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
127
|
-
| \`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) |
|
|
128
|
-
| \`textAlign: 'center'\` | \`textAlign="centered"\` (also \`left\`, \`right\`, \`justified\`) |
|
|
129
|
-
| \`color: '#…'\` | \`textColor\` with the nearest Bulma color: \`primary\`, \`link\`, \`info\`, \`success\`, \`warning\`, \`danger\`, \`white\`, \`black\`, \`grey\` (+ \`grey-light\`, \`grey-dark\`, …) |
|
|
130
|
-
| \`backgroundColor: '#…'\` | \`bgColor\` (same palette) |
|
|
131
|
-
| \`fontSize: …\` | \`textSize="1"\`…\`"7"\` (\`1\` largest) — for headings use \`Title\`/\`SubTitle\` \`size\` |
|
|
132
|
-
| \`fontWeight: …\` | \`textWeight\`: \`light\`, \`normal\`, \`medium\`, \`semibold\`, \`bold\` |
|
|
133
|
-
| \`textTransform\`, italics | \`textTransform\`: \`uppercase\`, \`lowercase\`, \`capitalized\`, \`italic\` |
|
|
134
|
-
| \`display: 'flex'\` + flex properties | same-named props: \`display="flex"\`, \`flexDirection\`, \`justifyContent\`, \`alignItems\`, \`flexWrap\` |
|
|
135
|
-
| \`height: '100%'\` on a flex child | \`flexGrow="1"\` |
|
|
136
|
-
| \`display: 'none'\` | \`visibility="hidden"\`, or responsive \`display*\` props (\`displayMobile\`, \`displayTablet\`, …) |
|
|
137
|
-
| \`gap: '1rem'\` (flex or grid) | \`gap="2"\`, or one axis: \`columnGap\`, \`rowGap\`; gap scale: \`1\`=0.5rem, \`2\`=1rem, \`4\`=2rem, half steps (\`"1.5"\`) |
|
|
138
|
-
|
|
139
|
-
- Spacing, typography, and flex helpers are on every component; \`textColor\`/\`bgColor\` are
|
|
140
|
-
on the content components (\`Box\`, \`Block\`, \`Title\`, \`Content\`, \`Hero\`, \`Card\`, …) — the
|
|
141
|
-
ones with a semantic \`color\` variant (\`Tag\`, \`Tabs\`, \`Panel\`) take \`color\` instead.
|
|
142
|
-
\`Notification\` is the mixed case: it takes \`textColor\`, but its background comes from
|
|
143
|
-
the semantic \`color\` prop, not \`bgColor\`.
|
|
144
|
-
- No helper matches (e.g. \`maxWidth\`, a one-off gradient)? Add a named class to
|
|
145
|
-
\`src/App.css\` and pass it via \`className\` — still never inline \`style\`.
|
|
146
|
-
- Don't hand-write Bulma utility classes either — bare text/markup has wrapper elements that
|
|
147
|
-
take the same helper props: \`Span\`, \`Paragraph\`, \`Strong\`, not \`<span className="has-text-…">\`.
|
|
169
|
+
${houseStyleOpening}
|
|
170
|
+
${utilityClassRule}
|
|
148
171
|
The one exception: companion classes Bulma requires on \`<html>\`/\`<body>\` (e.g.
|
|
149
172
|
\`has-navbar-fixed-top\` with \`Navbar fixed="top"\`) are hand-added in \`index.html\` — no
|
|
150
173
|
component renders those elements.
|
|
151
|
-
- Compound sub-parts (\`
|
|
174
|
+
- Compound sub-parts (\`Modal.*\`, \`Tabs.*\`, \`Message.*\`) take \`className\` + HTML
|
|
152
175
|
attributes and their own few props — no Bulma helper props, no \`as\`/\`href\`: nest a
|
|
153
176
|
\`Link\`/\`Span\` inside instead. \`Tabs.Tab\` and \`Tabs.Content.Item\` each require \`index={i}\`,
|
|
154
177
|
and \`Tabs.Tab\` has built-in \`icon\`/\`disabled\` props — no nested \`Icon\` needed.
|
|
@@ -250,15 +273,30 @@ export const ICON_LIBRARIES = [
|
|
|
250
273
|
color: chalk.yellow,
|
|
251
274
|
packageName: 'material-icons',
|
|
252
275
|
packageVersion: '^1.13.14',
|
|
253
|
-
|
|
276
|
+
// The package's bare import puts the filled, outlined, round, sharp and
|
|
277
|
+
// two-tone fonts into the build. Filled is the style Icon renders by
|
|
278
|
+
// default, so it is the only one imported.
|
|
279
|
+
importStatement: "import 'material-icons/iconfont/filled.css';",
|
|
280
|
+
setupInstructions: 'Only the filled style is loaded (`material-icons/iconfont/filled.css`, the variant `<Icon>` ' +
|
|
281
|
+
'uses by default). Import `outlined.css`, `round.css`, `sharp.css` or `two-tone.css` from ' +
|
|
282
|
+
'`material-icons/iconfont/` in `src/main.*` before using those variants.',
|
|
254
283
|
},
|
|
255
284
|
{
|
|
256
285
|
name: 'material-symbols',
|
|
257
286
|
display: 'Material Symbols',
|
|
258
287
|
color: chalk.magenta,
|
|
259
288
|
packageName: 'material-symbols',
|
|
260
|
-
|
|
261
|
-
|
|
289
|
+
// 0.x, so the caret holds the minor: this follows the newest minor
|
|
290
|
+
// bestax-bulma's material-symbols peer range admits, not the newest
|
|
291
|
+
// published one, or npm refuses the install as a peer conflict.
|
|
292
|
+
packageVersion: '^0.47.0',
|
|
293
|
+
// The package's bare import puts the outlined, rounded and sharp fonts
|
|
294
|
+
// into the build. Outlined is the style Icon renders by default, so it is
|
|
295
|
+
// the only one imported.
|
|
296
|
+
importStatement: "import 'material-symbols/outlined.css';",
|
|
297
|
+
setupInstructions: 'Only the outlined style is loaded (`material-symbols/outlined.css`, the variant `<Icon>` ' +
|
|
298
|
+
'uses by default). Import `material-symbols/rounded.css` or `sharp.css` in `src/main.*` ' +
|
|
299
|
+
'before using `variant="rounded"` or `"sharp"`.',
|
|
262
300
|
},
|
|
263
301
|
];
|
|
264
302
|
export const BULMA_FLAVORS = [
|
|
@@ -283,6 +321,7 @@ export const BULMA_FLAVORS = [
|
|
|
283
321
|
description: 'Core components only, no utility classes — helper props need them (~67 KB gzip)',
|
|
284
322
|
color: chalk.yellow,
|
|
285
323
|
importStatement: "import '@allxsmith/bestax-bulma/versions/bestax-no-helpers.css';",
|
|
324
|
+
noHelpers: true,
|
|
286
325
|
},
|
|
287
326
|
{
|
|
288
327
|
name: 'no-helpers-prefixed',
|
|
@@ -291,6 +330,7 @@ export const BULMA_FLAVORS = [
|
|
|
291
330
|
color: chalk.magenta,
|
|
292
331
|
importStatement: "import '@allxsmith/bestax-bulma/versions/bestax-no-helpers-prefixed.css';",
|
|
293
332
|
needsPrefix: true,
|
|
333
|
+
noHelpers: true,
|
|
294
334
|
},
|
|
295
335
|
{
|
|
296
336
|
name: 'no-dark-mode',
|
|
@@ -300,3 +340,34 @@ export const BULMA_FLAVORS = [
|
|
|
300
340
|
importStatement: "import '@allxsmith/bestax-bulma/versions/bestax-no-dark-mode.css';",
|
|
301
341
|
},
|
|
302
342
|
];
|
|
343
|
+
// Sits above the stylesheet import in the templates' entry files, and the
|
|
344
|
+
// scaffolder writes it back when it swaps in a flavor's stylesheet, so the
|
|
345
|
+
// reason for the import order survives. The no-helpers classes depend on it.
|
|
346
|
+
export const CSS_ORDER_COMMENT = "// Bestax's stylesheet loads before the app's own CSS, so a named class in\n" +
|
|
347
|
+
'// src/App.css wins over a Bulma rule of the same weight.';
|
|
348
|
+
// Every helper prop the starter App uses, with the named class that stands in
|
|
349
|
+
// for it under a `noHelpers` flavor, where the prop would render nothing. The
|
|
350
|
+
// scaffolder swaps each prop for its class and writes the rules into
|
|
351
|
+
// src/App.css, so the starter keeps its layout and shows that flavor's way of
|
|
352
|
+
// styling. A helper prop added to the starter needs a row here.
|
|
353
|
+
export const NO_HELPERS_STARTER_CLASSES = [
|
|
354
|
+
{
|
|
355
|
+
prop: 'textAlign="centered"',
|
|
356
|
+
className: 'page-title',
|
|
357
|
+
declaration: 'text-align: center;',
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
prop: 'display="flex"',
|
|
361
|
+
className: 'card-column',
|
|
362
|
+
declaration: 'display: flex;',
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
prop: 'flexGrow="1"',
|
|
366
|
+
className: 'card-fill',
|
|
367
|
+
declaration: 'flex-grow: 1;',
|
|
368
|
+
},
|
|
369
|
+
];
|
|
370
|
+
export const NO_HELPERS_APP_CSS = '\n/* This flavor of Bulma leaves out the helper classes, so helper props such\n' +
|
|
371
|
+
' as textAlign and flexGrow render nothing here. Named classes like these do\n' +
|
|
372
|
+
' the job instead: write one, then pass it with className. */\n' +
|
|
373
|
+
NO_HELPERS_STARTER_CLASSES.map(({ className, declaration }) => `.${className} {\n ${declaration}\n}\n`).join('\n');
|
|
@@ -42,9 +42,28 @@ export declare class ProjectCreator {
|
|
|
42
42
|
getTemplatePath(template: string): string;
|
|
43
43
|
copyTemplate(template: string, targetPath: string): Promise<void>;
|
|
44
44
|
updateIndexHtmlTitle(targetPath: string, projectName: string): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* The template README is written with pnpm commands and lists no icon
|
|
47
|
+
* library. Rewrite the commands for the package manager that ran the CLI
|
|
48
|
+
* (the one the success message names too) and list the icon library the
|
|
49
|
+
* user picked, if any.
|
|
50
|
+
*/
|
|
51
|
+
updateReadme(targetPath: string, iconLibrary: string): Promise<void>;
|
|
45
52
|
setupSkills(targetPath: string, projectName: string, options: ClaudeMdOptions): Promise<void>;
|
|
46
53
|
setupBulmaFlavor(targetPath: string, bulmaFlavor: string, template: string): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* A flavor without Bulma's helper classes leaves the starter's helper props
|
|
56
|
+
* rendering nothing, so its title loses its centering and its cards their
|
|
57
|
+
* equal heights. Swap each prop for the named class in
|
|
58
|
+
* NO_HELPERS_STARTER_CLASSES and append the classes' rules to src/App.css.
|
|
59
|
+
*/
|
|
60
|
+
replaceStarterHelperProps(targetPath: string, template: string): Promise<void>;
|
|
47
61
|
private getIconName;
|
|
62
|
+
/**
|
|
63
|
+
* The `iconProps` entries for a starter card icon. Decorative, since the
|
|
64
|
+
* card title beside it says the same thing, so it is hidden from screen
|
|
65
|
+
* readers rather than announced as "icon".
|
|
66
|
+
*/
|
|
48
67
|
private getIconProps;
|
|
49
68
|
setupIconLibrary(targetPath: string, iconLibrary: string, template: string): Promise<void>;
|
|
50
69
|
setupConfigProvider(targetPath: string, bulmaFlavor: string, iconLibrary: string, template: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-creator.d.ts","sourceRoot":"","sources":["../src/project-creator.ts"],"names":[],"mappings":"AA8BA,OAAO,
|
|
1
|
+
{"version":3,"file":"project-creator.d.ts","sourceRoot":"","sources":["../src/project-creator.ts"],"names":[],"mappings":"AA8BA,OAAO,EAWL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAYxB,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,WAAW,GAAG,MAAM,CAAC;AAEnE,qBAAa,cAAc;IACzB,OAAO,CAAC,YAAY,CAAS;gBAEjB,YAAY,CAAC,EAAE,MAAM;IAI3B,cAAc,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAajE;;;;;;;OAOG;IACG,sBAAsB,CAC1B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,UAAU,GACnB,OAAO,CAAC,qBAAqB,CAAC;IA2BjC;;;OAGG;IACH,eAAe,CAAC,OAAO,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI;IAmBpD,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAInC,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjE,oBAAoB,CACxB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC;IAgBhB;;;;;OAKG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBpE,WAAW,CACf,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,IAAI,CAAC;IAqBV,gBAAgB,CACpB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAyDhB;;;;;OAKG;IACG,yBAAyB,CAC7B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAmBhB,OAAO,CAAC,WAAW;IA2BnB;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAcd,gBAAgB,CACpB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAsJV,mBAAmB,CACvB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAmEV,MAAM,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAoItE;;;;;;;;OAQG;YACW,eAAe;CA2B9B"}
|
package/dist/project-creator.js
CHANGED
|
@@ -7,7 +7,8 @@ import { promptProjectName, promptOverwriteDirectory, promptTemplate, promptIcon
|
|
|
7
7
|
import { reportScaffold } from './telemetry.js';
|
|
8
8
|
import { displayHeader, displaySuccess, displayError, displayCancelled, } from './display.js';
|
|
9
9
|
import { validateProjectName, toValidPackageName } from './validators.js';
|
|
10
|
-
import { MESSAGES, TEMPLATES, ICON_LIBRARIES, BULMA_FLAVORS, CLAUDE_MD, LAUNCH_JSON, CONFIG_PROVIDER_ICON_VALUES, } from './constants.js';
|
|
10
|
+
import { MESSAGES, TEMPLATES, ICON_LIBRARIES, BULMA_FLAVORS, CLAUDE_MD, LAUNCH_JSON, CONFIG_PROVIDER_ICON_VALUES, NO_HELPERS_STARTER_CLASSES, NO_HELPERS_APP_CSS, CSS_ORDER_COMMENT, } from './constants.js';
|
|
11
|
+
import { detectPackageManager } from './package-manager.js';
|
|
11
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
13
|
// npm strips literal `.gitignore` files from published tarballs, so the
|
|
13
14
|
// templates track `_gitignore` instead and the scaffolder renames it back
|
|
@@ -100,6 +101,25 @@ export class ProjectCreator {
|
|
|
100
101
|
await fs.writeFile(indexHtmlPath, updated);
|
|
101
102
|
}
|
|
102
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* The template README is written with pnpm commands and lists no icon
|
|
106
|
+
* library. Rewrite the commands for the package manager that ran the CLI
|
|
107
|
+
* (the one the success message names too) and list the icon library the
|
|
108
|
+
* user picked, if any.
|
|
109
|
+
*/
|
|
110
|
+
async updateReadme(targetPath, iconLibrary) {
|
|
111
|
+
const readmePath = path.join(targetPath, 'README.md');
|
|
112
|
+
if (!fs.existsSync(readmePath))
|
|
113
|
+
return;
|
|
114
|
+
const pm = detectPackageManager();
|
|
115
|
+
let readme = await fs.readFile(readmePath, 'utf8');
|
|
116
|
+
readme = readme.replace(/\bpnpm (install|run)\b/g, `${pm} $1`);
|
|
117
|
+
const library = ICON_LIBRARIES.find(lib => lib.name === iconLibrary);
|
|
118
|
+
if (library && iconLibrary !== 'none') {
|
|
119
|
+
readme = readme.replace(/^(- .*bestax-bulma component library)$/m, `$1\n- 🎯 ${library.display} icons`);
|
|
120
|
+
}
|
|
121
|
+
await fs.writeFile(readmePath, readme);
|
|
122
|
+
}
|
|
103
123
|
async setupSkills(targetPath, projectName, options) {
|
|
104
124
|
const skillsSrc = path.join(this.templatesDir, 'skills');
|
|
105
125
|
if (!(await checkDirectoryExists(skillsSrc))) {
|
|
@@ -122,11 +142,13 @@ export class ProjectCreator {
|
|
|
122
142
|
const mainFilePath = path.join(targetPath, 'src', mainFileName);
|
|
123
143
|
if (fs.existsSync(mainFilePath)) {
|
|
124
144
|
let content = await fs.readFile(mainFilePath, 'utf8');
|
|
125
|
-
// Replace the default bestax CSS import
|
|
126
|
-
|
|
145
|
+
// Replace the default bestax CSS import, and the comment block directly
|
|
146
|
+
// above it, with the selected flavor's import under the comment that
|
|
147
|
+
// says why the stylesheets load before the app's own CSS.
|
|
148
|
+
const bestaxImportRegex = /(?:[ \t]*\/\/[^\n]*\n)+[ \t]*import\s+['"]@allxsmith\/bestax-bulma\/bestax\.css['"]\s*;?/;
|
|
127
149
|
const bestaxImportSimpleRegex = /import\s+['"]@allxsmith\/bestax-bulma\/bestax\.css['"]\s*;?/;
|
|
128
150
|
if (bestaxImportRegex.test(content)) {
|
|
129
|
-
content = content.replace(bestaxImportRegex,
|
|
151
|
+
content = content.replace(bestaxImportRegex, CSS_ORDER_COMMENT + '\n' + flavor.importStatement);
|
|
130
152
|
}
|
|
131
153
|
else if (bestaxImportSimpleRegex.test(content)) {
|
|
132
154
|
content = content.replace(bestaxImportSimpleRegex, flavor.importStatement);
|
|
@@ -145,6 +167,29 @@ export class ProjectCreator {
|
|
|
145
167
|
}
|
|
146
168
|
await fs.writeFile(mainFilePath, content);
|
|
147
169
|
}
|
|
170
|
+
if (flavor.noHelpers) {
|
|
171
|
+
await this.replaceStarterHelperProps(targetPath, template);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* A flavor without Bulma's helper classes leaves the starter's helper props
|
|
176
|
+
* rendering nothing, so its title loses its centering and its cards their
|
|
177
|
+
* equal heights. Swap each prop for the named class in
|
|
178
|
+
* NO_HELPERS_STARTER_CLASSES and append the classes' rules to src/App.css.
|
|
179
|
+
*/
|
|
180
|
+
async replaceStarterHelperProps(targetPath, template) {
|
|
181
|
+
const appFileName = template.includes('-ts') ? 'App.tsx' : 'App.jsx';
|
|
182
|
+
const appFilePath = path.join(targetPath, 'src', appFileName);
|
|
183
|
+
const appCssPath = path.join(targetPath, 'src', 'App.css');
|
|
184
|
+
if (!fs.existsSync(appFilePath) || !fs.existsSync(appCssPath))
|
|
185
|
+
return;
|
|
186
|
+
let appContent = await fs.readFile(appFilePath, 'utf8');
|
|
187
|
+
for (const { prop, className } of NO_HELPERS_STARTER_CLASSES) {
|
|
188
|
+
appContent = appContent.split(prop).join(`className="${className}"`);
|
|
189
|
+
}
|
|
190
|
+
await fs.writeFile(appFilePath, appContent);
|
|
191
|
+
const css = await fs.readFile(appCssPath, 'utf8');
|
|
192
|
+
await fs.writeFile(appCssPath, css.replace(/\n*$/, '\n') + NO_HELPERS_APP_CSS);
|
|
148
193
|
}
|
|
149
194
|
getIconName(iconLibrary, iconType) {
|
|
150
195
|
const iconMappings = {
|
|
@@ -168,13 +213,20 @@ export class ProjectCreator {
|
|
|
168
213
|
};
|
|
169
214
|
return iconMappings[iconLibrary]?.[iconType] || '';
|
|
170
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* The `iconProps` entries for a starter card icon. Decorative, since the
|
|
218
|
+
* card title beside it says the same thing, so it is hidden from screen
|
|
219
|
+
* readers rather than announced as "icon".
|
|
220
|
+
*/
|
|
171
221
|
getIconProps(iconLibrary, iconType) {
|
|
172
222
|
const iconName = this.getIconName(iconLibrary, iconType);
|
|
173
223
|
if (!iconName)
|
|
174
|
-
return
|
|
175
|
-
return
|
|
176
|
-
|
|
177
|
-
: `
|
|
224
|
+
return [];
|
|
225
|
+
return [
|
|
226
|
+
`name: '${iconName}'`,
|
|
227
|
+
...(iconLibrary === 'fontawesome' ? [`variant: 'solid'`] : []),
|
|
228
|
+
`'aria-hidden': 'true'`,
|
|
229
|
+
];
|
|
178
230
|
}
|
|
179
231
|
async setupIconLibrary(targetPath, iconLibrary, template) {
|
|
180
232
|
if (iconLibrary === 'none')
|
|
@@ -251,48 +303,48 @@ export class ProjectCreator {
|
|
|
251
303
|
}
|
|
252
304
|
}
|
|
253
305
|
}
|
|
254
|
-
// Now add
|
|
306
|
+
// Now add an icon to each card title in App.jsx/App.tsx (for all icon
|
|
307
|
+
// libraries). IconText spaces the icon from its text with Bulma's own
|
|
308
|
+
// .icon-text rule rather than a margin helper, so the gap holds in the
|
|
309
|
+
// no-helpers flavors too.
|
|
255
310
|
const isTypeScript = template.includes('-ts');
|
|
256
311
|
const appFileName = isTypeScript ? 'App.tsx' : 'App.jsx';
|
|
257
312
|
const appFilePath = path.join(targetPath, 'src', appFileName);
|
|
258
313
|
if (fs.existsSync(appFilePath)) {
|
|
259
314
|
let appContent = await fs.readFile(appFilePath, 'utf8');
|
|
260
|
-
// Add
|
|
315
|
+
// Add IconText to the bestax-bulma imports
|
|
261
316
|
const bulmaImportRegex = /(import\s+\{[\s\S]*?)(}\s+from\s+['"]@allxsmith\/bestax-bulma['"])/;
|
|
262
|
-
if (bulmaImportRegex.test(appContent) &&
|
|
263
|
-
|
|
317
|
+
if (bulmaImportRegex.test(appContent) &&
|
|
318
|
+
!/\bIconText\b/.test(appContent)) {
|
|
264
319
|
const importMatch = appContent.match(bulmaImportRegex);
|
|
265
320
|
if (importMatch) {
|
|
266
321
|
const beforeClosingBrace = importMatch[1];
|
|
267
|
-
// Remove any trailing comma and whitespace, then add
|
|
322
|
+
// Remove any trailing comma and whitespace, then add IconText properly
|
|
268
323
|
const cleanedImport = beforeClosingBrace.replace(/,?\s*$/, '');
|
|
269
|
-
appContent = appContent.replace(bulmaImportRegex, cleanedImport + ',\n
|
|
324
|
+
appContent = appContent.replace(bulmaImportRegex, cleanedImport + ',\n IconText,\n$2');
|
|
270
325
|
}
|
|
271
326
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (iconProps) {
|
|
294
|
-
appContent = appContent.replace(examplesRegex, `Card.Header.Title>\n <Icon ${iconProps} size="small" mr="2" />\n Examples\n </Card.Header.Title>`);
|
|
295
|
-
}
|
|
327
|
+
const cardIcons = [
|
|
328
|
+
['rocket', 'Quick Start'],
|
|
329
|
+
['book', 'Documentation'],
|
|
330
|
+
['code', 'Examples'],
|
|
331
|
+
];
|
|
332
|
+
for (const [iconType, title] of cardIcons) {
|
|
333
|
+
const iconProps = this.getIconProps(iconLibrary, iconType);
|
|
334
|
+
if (iconProps.length === 0)
|
|
335
|
+
continue;
|
|
336
|
+
const titleRegex = new RegExp(`^([ \\t]*)(<Card\\.Header\\.Title[^>]*>)\\s*${title}\\s*(</Card\\.Header\\.Title>)`, 'm');
|
|
337
|
+
appContent = appContent.replace(titleRegex, (_match, indent, open, close) => [
|
|
338
|
+
`${indent}${open}`,
|
|
339
|
+
`${indent} <IconText`,
|
|
340
|
+
`${indent} iconProps={{`,
|
|
341
|
+
...iconProps.map(entry => `${indent} ${entry},`),
|
|
342
|
+
`${indent} }}`,
|
|
343
|
+
`${indent} >`,
|
|
344
|
+
`${indent} ${title}`,
|
|
345
|
+
`${indent} </IconText>`,
|
|
346
|
+
`${indent}${close}`,
|
|
347
|
+
].join('\n'));
|
|
296
348
|
}
|
|
297
349
|
await fs.writeFile(appFilePath, appContent);
|
|
298
350
|
}
|
|
@@ -444,6 +496,7 @@ export class ProjectCreator {
|
|
|
444
496
|
await this.copyTemplate(template, targetPath);
|
|
445
497
|
await updatePackageJson(targetPath, packageName);
|
|
446
498
|
await this.updateIndexHtmlTitle(targetPath, projectName);
|
|
499
|
+
await this.updateReadme(targetPath, iconLibrary);
|
|
447
500
|
await this.setupBulmaFlavor(targetPath, bulmaFlavor, template);
|
|
448
501
|
await this.setupIconLibrary(targetPath, iconLibrary, template);
|
|
449
502
|
await this.setupConfigProvider(targetPath, bulmaFlavor, iconLibrary, template);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-bestax",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.16",
|
|
4
4
|
"description": "Create a new bestax-bulma project",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"prompts": "^2.4.2",
|
|
42
42
|
"react": "^18.0.0 || ^19.0.0",
|
|
43
43
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
44
|
-
"@allxsmith/bestax-bulma": "^5.27.
|
|
44
|
+
"@allxsmith/bestax-bulma": "^5.27.5"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@jest/globals": "^30.0.0",
|
|
@@ -33,7 +33,7 @@ Bulma forms are a three-tier structure. bestax models it directly:
|
|
|
33
33
|
|
|
34
34
|
```
|
|
35
35
|
Field // container + layout (horizontal / grouped / hasAddons)
|
|
36
|
-
├── label // rendered from Field's `label` prop, or <Field.Label>
|
|
36
|
+
├── label // rendered from Field's `label` prop, or a <label> you put in <Field.Label>
|
|
37
37
|
└── Control // wraps ONE input; adds icons + loading
|
|
38
38
|
├── InputBase / SelectBase / TextAreaBase // the raw styled element
|
|
39
39
|
└── <p class="help">…</p> // help / validation message
|
|
@@ -131,13 +131,24 @@ The `label` prop on the single-control convenience inputs (`Input`, `Select`, `T
|
|
|
131
131
|
generated one otherwise, and an explicit `labelProps={{ htmlFor }}` wins. The wiring only
|
|
132
132
|
happens when the component renders its own `Field` (nested inside one, the `label` prop is
|
|
133
133
|
dropped); the date/time pickers skip it in `inline` mode and `Taginput` skips it at
|
|
134
|
-
`maxTags` (no visible input to label).
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
`
|
|
139
|
-
|
|
140
|
-
|
|
134
|
+
`maxTags` (no visible input to label). A `range` `Slider` points the `for` at its low thumb
|
|
135
|
+
and also puts the label at the start of both thumbs' names through `aria-labelledby`
|
|
136
|
+
("Price range Minimum value"). Its `ariaLabel={[low, high]}` replaces those names outright, so
|
|
137
|
+
leave it off when a label already names the Slider. The group inputs (`Checkboxes`, `Radios`, `Rate`,
|
|
138
|
+
`DateRangeInput`) associate their `label` too, but group-style: the wrapper gets
|
|
139
|
+
`role="group"`/`"radiogroup"` and `aria-labelledby` pointing at the label. Composing yourself
|
|
140
|
+
also associates: a labeled `Field` names the one control it holds, whether a composed
|
|
141
|
+
`InputBase`/`SelectBase`/`TextAreaBase`, a composed `DateInputBase`/`TimeInputBase`/
|
|
142
|
+
`DateTimeInputBase` that is not `inline`, or any input above (through the id), or a group
|
|
143
|
+
(through `aria-labelledby`). Either way, an `aria-label` or `aria-labelledby` you give a
|
|
144
|
+
group wins over the label. A `Checkbox`, `Radio` or `Switch` takes nothing from a `Field`:
|
|
145
|
+
each is named by its own children, so put the text there. The association is skipped for
|
|
146
|
+
`grouped`/`hasAddons`, and a nested `Field` starts its own scope, so a
|
|
147
|
+
horizontal `Field` whose body holds an inner `Field` needs `labelProps={{ htmlFor }}` plus the
|
|
148
|
+
control's `id` (for a group, `labelProps={{ id, htmlFor: undefined }}` plus its
|
|
149
|
+
`aria-labelledby`, since nothing takes the label's `for` there). Pass
|
|
150
|
+
`labelProps={{ htmlFor }}` plus a matching `id` only when you want a stable id, or
|
|
151
|
+
`labelProps={{ htmlFor: undefined }}` to opt out.
|
|
141
152
|
|
|
142
153
|
## Convenience vs composed
|
|
143
154
|
|
|
@@ -8,16 +8,16 @@ via `useBulmaClasses`.
|
|
|
8
8
|
|
|
9
9
|
Container and layout. Compound parts: `Field.Label`, `Field.Body`.
|
|
10
10
|
|
|
11
|
-
| Prop | Type | Notes
|
|
12
|
-
| ----------------------- | ------------------------------------------------- |
|
|
13
|
-
| `horizontal` | `boolean` | Label + control side by side. Auto-wraps children in `Field.Body`.
|
|
14
|
-
| `grouped` | `boolean \| 'centered' \| 'right' \| 'multiline'` | Controls in a row.
|
|
15
|
-
| `hasAddons` | `boolean \| 'centered' \| 'right'` | Attached controls.
|
|
16
|
-
| `narrow` | `boolean` | Constrain to content width (inside horizontal bodies).
|
|
17
|
-
| `label` | `ReactNode` |
|
|
18
|
-
| `labelSize` | `'small' \| 'normal' \| 'medium' \| 'large'` | Label size.
|
|
19
|
-
| `labelProps` | label attributes | Props for the `<label>`. An explicit `htmlFor` (even `undefined`) takes over the association; otherwise it is set automatically. |
|
|
20
|
-
| `textColor` / `bgColor` | Bulma color | Helper colors for the field.
|
|
11
|
+
| Prop | Type | Notes |
|
|
12
|
+
| ----------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| `horizontal` | `boolean` | Label + control side by side. Auto-wraps children in `Field.Body`. |
|
|
14
|
+
| `grouped` | `boolean \| 'centered' \| 'right' \| 'multiline'` | Controls in a row. |
|
|
15
|
+
| `hasAddons` | `boolean \| 'centered' \| 'right'` | Attached controls. |
|
|
16
|
+
| `narrow` | `boolean` | Constrain to content width (inside horizontal bodies). |
|
|
17
|
+
| `label` | `ReactNode` | Names the one control it holds: a composed base or convenience input through its id, a group through `aria-labelledby` (skipped for `grouped`/`hasAddons`; a nested `Field` starts its own scope). |
|
|
18
|
+
| `labelSize` | `'small' \| 'normal' \| 'medium' \| 'large'` | Label size. |
|
|
19
|
+
| `labelProps` | label attributes | Props for the `<label>`. An explicit `htmlFor` (even `undefined`) takes over the association; otherwise it is set automatically. An `id` here is what a group's `aria-labelledby` points at. |
|
|
20
|
+
| `textColor` / `bgColor` | Bulma color | Helper colors for the field. |
|
|
21
21
|
|
|
22
22
|
## Control — `form/Control.tsx`
|
|
23
23
|
|
|
@@ -170,12 +170,23 @@ Recipes built on addons, each written out in full in the docs:
|
|
|
170
170
|
|
|
171
171
|
## Horizontal field with explicit label/body
|
|
172
172
|
|
|
173
|
+
`Field.Label` is the label column, not a `<label>`, so text placed straight in it names nothing:
|
|
174
|
+
put a `<label className="label">` inside it with `htmlFor` pointing at the input's `id`.
|
|
175
|
+
|
|
173
176
|
```tsx
|
|
174
177
|
<Field horizontal>
|
|
175
|
-
<Field.Label size="normal">
|
|
178
|
+
<Field.Label size="normal">
|
|
179
|
+
<label className="label" htmlFor="horizontal-email">
|
|
180
|
+
Email
|
|
181
|
+
</label>
|
|
182
|
+
</Field.Label>
|
|
176
183
|
<Field.Body>
|
|
177
184
|
<Control iconLeftName="envelope" hasIconsLeft>
|
|
178
|
-
<InputBase
|
|
185
|
+
<InputBase
|
|
186
|
+
id="horizontal-email"
|
|
187
|
+
type="email"
|
|
188
|
+
placeholder="you@example.com"
|
|
189
|
+
/>
|
|
179
190
|
</Control>
|
|
180
191
|
</Field.Body>
|
|
181
192
|
</Field>
|
|
@@ -203,7 +214,15 @@ const [volume, setVolume] = useState(50);
|
|
|
203
214
|
<Slider value={volume} onChange={setVolume} min={0} max={100} tooltip="auto" />;
|
|
204
215
|
|
|
205
216
|
const [range, setRange] = useState<[number, number]>([20, 80]);
|
|
206
|
-
|
|
217
|
+
// The label names both thumbs: "Price range Minimum value" / "... Maximum value".
|
|
218
|
+
<Slider
|
|
219
|
+
range
|
|
220
|
+
label="Price range"
|
|
221
|
+
value={range}
|
|
222
|
+
onChange={setRange}
|
|
223
|
+
min={0}
|
|
224
|
+
max={100}
|
|
225
|
+
/>;
|
|
207
226
|
|
|
208
227
|
// Numberinput
|
|
209
228
|
const [qty, setQty] = useState(1);
|