create-bestax 4.2.14 → 4.2.15

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.
@@ -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
@@ -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;AAED,eAAO,MAAM,SAAS,GACpB,aAAa,MAAM,EACnB,8BAA8B,eAAe,KAC5C,MAmIF,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"}
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
- **Never inline \`style={{}}\`** — the components accept helper props that cover the common
123
- cases. Before writing \`style\`, translate each declaration with this table:
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 (\`Card.*\`, \`Modal.*\`, \`Tabs.*\`, \`Message.*\`) take \`className\` + HTML
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
- importStatement: "import 'material-icons';",
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
- packageVersion: '^0.45.2',
261
- importStatement: "import 'material-symbols';",
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.46.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,EAQL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAWxB,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;IAgBV,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;IAmDhB,OAAO,CAAC,WAAW;IA2BnB,OAAO,CAAC,YAAY;IAYd,gBAAgB,CACpB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IA4JV,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;IAmItE;;;;;;;;OAQG;YACW,eAAe;CA2B9B"}
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"}
@@ -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 with the selected flavor
126
- const bestaxImportRegex = /\/\/.*\n\s*import\s+['"]@allxsmith\/bestax-bulma\/bestax\.css['"]\s*;?/;
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, '// Import CSS\n' + flavor.importStatement);
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 iconLibrary === 'fontawesome'
176
- ? `name="${iconName}" variant="solid"`
177
- : `name="${iconName}"`;
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 Icon components to App.jsx/App.tsx (for all icon libraries)
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 Icon import to the bestax-bulma imports
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) && !appContent.includes('Icon')) {
263
- // Check if the import ends with a comma or not
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 Icon properly
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 Icon$2');
324
+ appContent = appContent.replace(bulmaImportRegex, cleanedImport + ',\n IconText,\n$2');
270
325
  }
271
326
  }
272
- // Add icon examples in the Cards section
273
- // Find the Quick Start Card and add an icon
274
- const quickStartRegex = /(Card\.Header\.Title>\s*Quick Start\s*<\/Card\.Header\.Title>)/;
275
- if (quickStartRegex.test(appContent)) {
276
- const iconProps = this.getIconProps(iconLibrary, 'rocket');
277
- if (iconProps) {
278
- appContent = appContent.replace(quickStartRegex, `Card.Header.Title>\n <Icon ${iconProps} size="small" mr="2" />\n Quick Start\n </Card.Header.Title>`);
279
- }
280
- }
281
- // Add icon to Documentation Card
282
- const docsRegex = /(Card\.Header\.Title>\s*Documentation\s*<\/Card\.Header\.Title>)/;
283
- if (docsRegex.test(appContent)) {
284
- const iconProps = this.getIconProps(iconLibrary, 'book');
285
- if (iconProps) {
286
- appContent = appContent.replace(docsRegex, `Card.Header.Title>\n <Icon ${iconProps} size="small" mr="2" />\n Documentation\n </Card.Header.Title>`);
287
- }
288
- }
289
- // Add icon to Examples Card
290
- const examplesRegex = /(Card\.Header\.Title>\s*Examples\s*<\/Card\.Header\.Title>)/;
291
- if (examplesRegex.test(appContent)) {
292
- const iconProps = this.getIconProps(iconLibrary, 'code');
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.14",
3
+ "version": "4.2.15",
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.1"
44
+ "@allxsmith/bestax-bulma": "^5.27.4"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@jest/globals": "^30.0.0",
@@ -131,13 +131,23 @@ 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). The group inputs (`Checkboxes`, `Radios`, `Rate`,
135
- `DateRangeInput`) associate their `label` too, but group-style: the wrapper gets `role="group"`/`"radiogroup"`
136
- and `aria-labelledby` pointing at the label. Composing `Field` + bases yourself also
137
- associates: `Field`'s own `label` wires to a single composed `InputBase`/`SelectBase`/
138
- `TextAreaBase` (skipped for `grouped`/`hasAddons`). Pass `labelProps={{ htmlFor }}` plus a
139
- matching `id` only when you want a stable id, or `labelProps={{ htmlFor: undefined }}` to
140
- opt out — e.g. when the labeled `Field` wraps something that is not one of those bases.
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` or any input above (through the id), or a group
142
+ (through `aria-labelledby`). Either way, an `aria-label` or `aria-labelledby` you give a
143
+ group wins over the label. A `Checkbox`, `Radio` or `Switch` takes nothing from a `Field`:
144
+ each is named by its own children, so put the text there. The association is skipped for
145
+ `grouped`/`hasAddons`, and a nested `Field` starts its own scope, so a
146
+ horizontal `Field` whose body holds an inner `Field` needs `labelProps={{ htmlFor }}` plus the
147
+ control's `id` (for a group, `labelProps={{ id, htmlFor: undefined }}` plus its
148
+ `aria-labelledby`, since nothing takes the label's `for` there). Pass
149
+ `labelProps={{ htmlFor }}` plus a matching `id` only when you want a stable id, or
150
+ `labelProps={{ htmlFor: undefined }}` to opt out.
141
151
 
142
152
  ## Convenience vs composed
143
153
 
@@ -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` | Convenience label; auto-associates with a single composed `InputBase`/`SelectBase`/`TextAreaBase` (skipped for `grouped`/`hasAddons`). |
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
 
@@ -203,7 +203,15 @@ const [volume, setVolume] = useState(50);
203
203
  <Slider value={volume} onChange={setVolume} min={0} max={100} tooltip="auto" />;
204
204
 
205
205
  const [range, setRange] = useState<[number, number]>([20, 80]);
206
- <Slider range value={range} onChange={setRange} min={0} max={100} />;
206
+ // The label names both thumbs: "Price range Minimum value" / "... Maximum value".
207
+ <Slider
208
+ range
209
+ label="Price range"
210
+ value={range}
211
+ onChange={setRange}
212
+ min={0}
213
+ max={100}
214
+ />;
207
215
 
208
216
  // Numberinput
209
217
  const [qty, setQty] = useState(1);
@@ -67,8 +67,11 @@ Set it once on `ConfigProvider` and omit `library` everywhere else.
67
67
 
68
68
  ## Google Material Icons — `'material-icons'`
69
69
 
70
- - **Setup:** `npm install material-icons` and `import 'material-icons';`.
71
- - **Scaffold flag:** `--icon material-icons`.
70
+ - **Setup:** `npm install material-icons` and `import 'material-icons/iconfont/filled.css';`, the
71
+ style `Icon` renders by default. Add `outlined.css`, `round.css`, `sharp.css` or `two-tone.css`
72
+ from `material-icons/iconfont/` for those variants; the bare `import 'material-icons';` loads
73
+ all five fonts.
74
+ - **Scaffold flag:** `--icon material-icons` (imports `filled.css` only).
72
75
  - **Names:** snake_case ligature text: `home`, `rocket_launch`, `shopping_cart`. A kebab-case
73
76
  name will not match a ligature and renders as raw text.
74
77
  - **Variants:** `filled` (default) / `outlined` / `round` / `sharp` — note **`round`**, not
@@ -77,8 +80,10 @@ Set it once on `ConfigProvider` and omit `library` everywhere else.
77
80
 
78
81
  ## Material Symbols — `'material-symbols'`
79
82
 
80
- - **Setup:** `npm install material-symbols` and `import 'material-symbols';`.
81
- - **Scaffold flag:** `--icon material-symbols`.
83
+ - **Setup:** `npm install material-symbols` and `import 'material-symbols/outlined.css';`, the
84
+ style `Icon` renders by default. Add `rounded.css` or `sharp.css` for those variants; the bare
85
+ `import 'material-symbols';` loads all three fonts.
86
+ - **Scaffold flag:** `--icon material-symbols` (imports `outlined.css` only).
82
87
  - **Names:** snake_case ligature text, same as Material Icons: `rocket_launch`.
83
88
  - **Variants:** `outlined` (default) / `rounded` / `sharp` — note **`rounded`** here vs
84
89
  Material Icons' `round`.
@@ -321,6 +321,14 @@ renders `has-text-<color>` too, and so does `is-centered`, which `isCentered` re
321
321
  beside `isRight`. Anything else keeps it as markup with a `children:File`, `context:File` or
322
322
  `attr` TODO.
323
323
 
324
+ A `has-name` `.file` with no `.file-name` converts only with Bulma's `is-empty` written on it as a
325
+ static class, since `File` renders `is-empty` there itself whatever a condition says, so a
326
+ condition on it can't stand in. The class then goes with the rest of the tree. The `File` gets
327
+ `fileName=""`, which pins its name empty the way a `.file-name`'s text pins it to that text. With
328
+ no `fileName`, `File` would show the name of the file a user picks and drop `is-empty`, which the
329
+ markup never does. Without a static `is-empty` the `.file` stays markup with a `defaults:File` TODO
330
+ that names the class to write.
331
+
324
332
  ## Families this source leaves as markup
325
333
 
326
334
  Their markup doesn't map element by element (the bestax component renders parts of its own, or
@@ -88,6 +88,11 @@ element the attribute it's missing.
88
88
  sets none, and `Card.Header.Icon` renders `aria-label="more options"` too. One whose `type` isn't
89
89
  `button`, `submit` or `reset` gets an `attr:type` TODO instead, since bestax writes `button` in its
90
90
  place. Write the type you mean, then re-run.
91
+ `File` renders a class rather than an attribute: Bulma's `is-empty`, on a `has-name` `.file` with
92
+ no `.file-name`, whatever a condition says. Write `is-empty` on the `.file` as a static class, then
93
+ re-run: a condition on `is-empty` can't stand in, so one already there still gets this TODO. The
94
+ `File` it becomes gets `fileName=""`, so a file the user picks leaves it as the markup was: no name
95
+ area, and `is-empty` still on.
91
96
 
92
97
  ### `drops:<Target>`
93
98