create-bestax 3.3.1 → 3.3.2

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.
@@ -12,6 +12,7 @@ export declare const MESSAGES: {
12
12
  readonly PROJECT_NAME_REQUIRED: "Project name is required";
13
13
  readonly PROJECT_NAME_TOO_LONG: "Project name too long";
14
14
  readonly PROJECT_NAME_INVALID_CHARS: "Project name can only contain letters, numbers, dots, dashes and underscores";
15
+ readonly PROJECT_NAME_DOT: "Project name cannot start with a dot (names like \".\" or \"..\" would scaffold outside a new directory) — pass a directory name";
15
16
  readonly OPERATION_CANCELLED: "✖ Operation cancelled";
16
17
  readonly NO_TTY: string;
17
18
  readonly DIRECTORY_NOT_EMPTY: (dir: string) => string;
@@ -41,6 +42,7 @@ export interface IconLibrary {
41
42
  display: string;
42
43
  color: typeof chalk.yellow;
43
44
  packageName?: string;
45
+ packageVersion?: string;
44
46
  importStatement?: string;
45
47
  setupInstructions?: string;
46
48
  }
@@ -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;;;;;;wCAWQ,MAAM;uCAEP,MAAM;sCACP,MAAM;4CAEA,MAAM;;;;;CAM7B,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,MA+DF,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;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,cAAc,EAAE,WAAW,EAyCvC,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,EA0CtC,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;AAEtD,eAAO,MAAM,QAAQ;;;;;;;wCAaQ,MAAM;uCAEP,MAAM;sCACP,MAAM;4CAEA,MAAM;;;;;CAM7B,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,MA+DF,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
@@ -10,6 +10,7 @@ export const MESSAGES = {
10
10
  PROJECT_NAME_REQUIRED: 'Project name is required',
11
11
  PROJECT_NAME_TOO_LONG: 'Project name too long',
12
12
  PROJECT_NAME_INVALID_CHARS: 'Project name can only contain letters, numbers, dots, dashes and underscores',
13
+ PROJECT_NAME_DOT: 'Project name cannot start with a dot (names like "." or ".." would scaffold outside a new directory) — pass a directory name',
13
14
  OPERATION_CANCELLED: '✖ Operation cancelled',
14
15
  NO_TTY: 'No interactive terminal detected — cannot prompt for input.\n' +
15
16
  'Re-run non-interactively with a project name and flags, e.g.:\n' +
@@ -113,6 +114,7 @@ export const ICON_LIBRARIES = [
113
114
  display: 'Font Awesome',
114
115
  color: chalk.blue,
115
116
  packageName: '@fortawesome/fontawesome-free',
117
+ packageVersion: '^7.2.0',
116
118
  importStatement: "import '@fortawesome/fontawesome-free/css/all.min.css';",
117
119
  },
118
120
  {
@@ -120,6 +122,7 @@ export const ICON_LIBRARIES = [
120
122
  display: 'Material Design Icons',
121
123
  color: chalk.cyan,
122
124
  packageName: '@mdi/font',
125
+ packageVersion: '^7.4.47',
123
126
  importStatement: "import '@mdi/font/css/materialdesignicons.min.css';",
124
127
  },
125
128
  {
@@ -134,6 +137,7 @@ export const ICON_LIBRARIES = [
134
137
  display: 'Google Material Icons',
135
138
  color: chalk.yellow,
136
139
  packageName: 'material-icons',
140
+ packageVersion: '^1.13.14',
137
141
  importStatement: "import 'material-icons';",
138
142
  },
139
143
  {
@@ -141,6 +145,7 @@ export const ICON_LIBRARIES = [
141
145
  display: 'Material Symbols',
142
146
  color: chalk.magenta,
143
147
  packageName: 'material-symbols',
148
+ packageVersion: '^0.45.2',
144
149
  importStatement: "import 'material-symbols';",
145
150
  },
146
151
  ];
@@ -148,14 +153,14 @@ export const BULMA_FLAVORS = [
148
153
  {
149
154
  name: 'complete',
150
155
  display: 'Complete (Recommended)',
151
- description: 'Full Bulma CSS with all components and helpers',
156
+ description: 'Full Bulma CSS with all components and helpers (~82 KB gzip)',
152
157
  color: chalk.green,
153
158
  importStatement: "import '@allxsmith/bestax-bulma/bestax.css';",
154
159
  },
155
160
  {
156
161
  name: 'prefixed',
157
162
  display: 'Prefixed',
158
- description: 'All classes prefixed with "bestax-" to avoid conflicts',
163
+ description: 'All classes prefixed with "bestax-" to avoid conflicts (~84 KB gzip)',
159
164
  color: chalk.blue,
160
165
  importStatement: "import '@allxsmith/bestax-bulma/versions/bestax-prefixed.css';",
161
166
  needsPrefix: true,
@@ -163,14 +168,14 @@ export const BULMA_FLAVORS = [
163
168
  {
164
169
  name: 'no-helpers',
165
170
  display: 'No Helpers',
166
- description: 'Core components only, no utility classes',
171
+ description: 'Core components only, no utility classes — helper props need them (~67 KB gzip)',
167
172
  color: chalk.yellow,
168
173
  importStatement: "import '@allxsmith/bestax-bulma/versions/bestax-no-helpers.css';",
169
174
  },
170
175
  {
171
176
  name: 'no-helpers-prefixed',
172
177
  display: 'No Helpers, Prefixed',
173
- description: 'Core components only with "bestax-" prefix',
178
+ description: 'Core components only with "bestax-" prefix (~69 KB gzip)',
174
179
  color: chalk.magenta,
175
180
  importStatement: "import '@allxsmith/bestax-bulma/versions/bestax-no-helpers-prefixed.css';",
176
181
  needsPrefix: true,
@@ -178,7 +183,7 @@ export const BULMA_FLAVORS = [
178
183
  {
179
184
  name: 'no-dark-mode',
180
185
  display: 'No Dark Mode',
181
- description: 'Light mode only, smaller bundle size',
186
+ description: 'Light mode only, smaller bundle size (~70 KB gzip)',
182
187
  color: chalk.cyan,
183
188
  importStatement: "import '@allxsmith/bestax-bulma/versions/bestax-no-dark-mode.css';",
184
189
  },
@@ -1 +1 @@
1
- {"version":3,"file":"project-creator.d.ts","sourceRoot":"","sources":["../src/project-creator.ts"],"names":[],"mappings":"AA2BA,OAAO,EAML,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAIxB,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,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,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;IAa3D,sBAAsB,CAC1B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC;IAkBnB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAInC,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjE,WAAW,CACf,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,IAAI,CAAC;IAiBV,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;IA2IV,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;CAwIvE"}
1
+ {"version":3,"file":"project-creator.d.ts","sourceRoot":"","sources":["../src/project-creator.ts"],"names":[],"mappings":"AA2BA,OAAO,EAML,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAIxB,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,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,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;IAa3D,sBAAsB,CAC1B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC;IAkBnB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAInC,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjE,WAAW,CACf,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,IAAI,CAAC;IAiBV,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;IAgJV,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;CAwIvE"}
@@ -135,6 +135,10 @@ export class ProjectCreator {
135
135
  const headEndMatch = htmlContent.match(/<\/head>/);
136
136
  if (headEndMatch) {
137
137
  const insertPosition = headEndMatch.index;
138
+ // The ionicons version is pinned to an immutable unpkg URL. SRI
139
+ // integrity attributes are deliberately not added: the ionicons ESM
140
+ // loader dynamically imports per-icon chunks that cannot carry
141
+ // integrity hashes, so entry-file SRI would give false assurance.
138
142
  const ioniconScripts = ` <!-- Ionicons -->
139
143
  <script type="module" src="https://unpkg.com/ionicons@8.0.13/dist/ionicons/ionicons.esm.js"></script>
140
144
  <script nomodule src="https://unpkg.com/ionicons@8.0.13/dist/ionicons/ionicons.js"></script>
@@ -159,7 +163,8 @@ export class ProjectCreator {
159
163
  packageJson.dependencies = {};
160
164
  }
161
165
  if (library.packageName) {
162
- packageJson.dependencies[library.packageName] = 'latest';
166
+ packageJson.dependencies[library.packageName] =
167
+ library.packageVersion ?? 'latest';
163
168
  }
164
169
  await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
165
170
  // Add import statement to main file
@@ -1 +1 @@
1
- {"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../src/validators.ts"],"names":[],"mappings":"AAMA,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAcnE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD"}
1
+ {"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../src/validators.ts"],"names":[],"mappings":"AAMA,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAqBnE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD"}
@@ -6,6 +6,12 @@ export function validateProjectName(value) {
6
6
  if (value.length > MAX_PROJECT_NAME_LENGTH) {
7
7
  return MESSAGES.PROJECT_NAME_TOO_LONG;
8
8
  }
9
+ // The regex below allows dots, so "." and ".." would pass and resolve to
10
+ // the current/parent directory — which --yes would then empty. Rejecting
11
+ // every leading-dot name closes that hole ("/" and "\" are already blocked).
12
+ if (value.startsWith('.')) {
13
+ return MESSAGES.PROJECT_NAME_DOT;
14
+ }
9
15
  if (!PROJECT_NAME_REGEX.test(value)) {
10
16
  return MESSAGES.PROJECT_NAME_INVALID_CHARS;
11
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-bestax",
3
- "version": "3.3.1",
3
+ "version": "3.3.2",
4
4
  "description": "Create a new bestax-bulma project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: bestax-icons
3
+ description: Use icons in an app built with @allxsmith/bestax-bulma — the Icon/IconText components and the five supported libraries (Font Awesome, Material Design Icons, Ionicons, Google Material Icons, Material Symbols). Use when adding an icon, choosing or configuring the app-wide icon library, fixing an icon that renders blank, pairing icons with text, or making icons accessible (decorative vs labeled).
4
+ license: MIT
5
+ ---
6
+
7
+ # Icons with @allxsmith/bestax-bulma
8
+
9
+ `Icon` renders a Bulma icon container (`span.icon`) around a glyph from any of five icon
10
+ libraries behind one normalized API. `IconText` pairs icons with text. The library ships **no
11
+ icon fonts** — the chosen library's package (or CDN script) must be installed in the app.
12
+
13
+ ## Quick start
14
+
15
+ ```tsx
16
+ import { ConfigProvider, Icon, IconText } from '@allxsmith/bestax-bulma';
17
+
18
+ // Set the library ONCE at the app root; <Icon> then never needs `library`.
19
+ <ConfigProvider iconLibrary="fa">
20
+ <App />
21
+ </ConfigProvider>;
22
+
23
+ // Inside the app:
24
+ <Icon name="rocket" ariaLabel="Launch" />;
25
+ <IconText iconProps={{ name: 'star', 'aria-hidden': 'true' }}>
26
+ Starred
27
+ </IconText>;
28
+ ```
29
+
30
+ ## The five libraries
31
+
32
+ | Library | `iconLibrary` / `library` value | Name format | Example `name` |
33
+ | ---------------------- | ------------------------------- | ---------------------------- | --------------- |
34
+ | Font Awesome (default) | `'fa'` | kebab-case, no `fa-` prefix | `rocket` |
35
+ | Material Design Icons | `'mdi'` | kebab-case, no `mdi-` prefix | `rocket-launch` |
36
+ | Ionicons | `'ion'` | kebab-case | `rocket` |
37
+ | Google Material Icons | `'material-icons'` | snake_case (a text ligature) | `rocket_launch` |
38
+ | Material Symbols | `'material-symbols'` | snake_case (a text ligature) | `rocket_launch` |
39
+
40
+ ⚠️ **The Ionicons value is `'ion'`, not `'ionicons'`.** The `npm create bestax` scaffold's
41
+ `--icon ionicons` flag maps to `iconLibrary="ion"` — passing `'ionicons'` to `ConfigProvider`
42
+ or `library` silently renders nothing.
43
+
44
+ **The same glyph has a different name per library** (`rocket` vs `rocket-launch` vs
45
+ `rocket_launch`). When an icon renders blank, the name format for the active library is the
46
+ first thing to check. A redundant `fa-`/`mdi-` prefix in `name` is tolerated (stripped), but
47
+ don't rely on it.
48
+
49
+ ## Styling
50
+
51
+ - `size` — `'small' | 'medium' | 'large'` sizes the Bulma **container** (`is-small` ≈ 1rem,
52
+ `is-medium` ≈ 2rem, `is-large` ≈ 3rem box). To scale the **glyph**, use `features`
53
+ (Font Awesome `'fa-lg'`/`'fa-2x'`) or a Bulma text-size class (`'is-size-3'`).
54
+ - `variant` — per-library style: Font Awesome `solid` (default) / `regular` / `brands` /
55
+ `light` / `duotone` / `thin`; Material Icons `filled` (default) / `outlined` / `round` /
56
+ `sharp`; Material Symbols `outlined` (default) / `rounded` / `sharp`; Ionicons `outline` /
57
+ `sharp`. MDI has no variants. Note Material **Icons** uses `round`, Material **Symbols**
58
+ uses `rounded`.
59
+ - `features` — extra library classes, string or array: `'fa-spin'`, `['fa-lg', 'fa-border']`.
60
+ - Color via the helper props: `textColor="primary"`, `textColor="danger"`, etc.
61
+
62
+ ## Accessibility
63
+
64
+ Every `Icon` renders `aria-label` (default `"icon"`).
65
+
66
+ - **Meaningful icon** (stands alone, conveys information): pass a descriptive
67
+ `ariaLabel="Delete item"`.
68
+ - **Decorative icon** (next to visible text that says the same thing, e.g. inside `IconText`
69
+ or a labeled `Button`): hide it from screen readers with `aria-hidden`:
70
+ `<Icon name="check" aria-hidden="true" />` — otherwise "icon" (or a duplicate label) is
71
+ announced alongside the text.
72
+
73
+ ## References
74
+
75
+ - `references/icon-libraries.md` — per-library setup (install/import/CDN), the full
76
+ name-format and variant tables, `features` values, and the blank-icon troubleshooting list.
77
+ - `examples/icon-usage.tsx` — runnable example: ConfigProvider setup, sizes, variants,
78
+ colors, IconText, and decorative-vs-labeled patterns.
@@ -0,0 +1,80 @@
1
+ // Icons with @allxsmith/bestax-bulma — the canonical patterns.
2
+ //
3
+ // The app must load the icon library itself (bestax ships no icon fonts):
4
+ // here Font Awesome, via `npm install @fortawesome/fontawesome-free` and
5
+ // `import '@fortawesome/fontawesome-free/css/all.min.css';` in main.tsx.
6
+ // Set the library ONCE on ConfigProvider ('fa' | 'mdi' | 'ion' |
7
+ // 'material-icons' | 'material-symbols' — Ionicons is 'ion', NOT 'ionicons');
8
+ // every <Icon> below then omits `library`.
9
+ import React from 'react';
10
+ import {
11
+ ConfigProvider,
12
+ Icon,
13
+ IconText,
14
+ Button,
15
+ Box,
16
+ Title,
17
+ } from '@allxsmith/bestax-bulma';
18
+
19
+ export function IconShowcase() {
20
+ return (
21
+ <ConfigProvider iconLibrary="fa">
22
+ <Box>
23
+ <Title size="5">Meaningful icons carry their own label</Title>
24
+ {/* Standalone icons convey information — give them a descriptive
25
+ ariaLabel (the default is just "icon"). */}
26
+ <Icon
27
+ name="triangle-exclamation"
28
+ textColor="warning"
29
+ ariaLabel="Warning"
30
+ />
31
+ <Icon name="circle-check" textColor="success" ariaLabel="Success" />
32
+
33
+ <Title size="5" mt="4">
34
+ Decorative icons hide from screen readers
35
+ </Title>
36
+ {/* Next to visible text the icon repeats the message — aria-hidden
37
+ stops "icon" (or a duplicate) being announced. */}
38
+ <IconText iconProps={{ name: 'star', 'aria-hidden': 'true' }}>
39
+ Starred
40
+ </IconText>
41
+ <Button color="primary">
42
+ <Icon name="floppy-disk" aria-hidden="true" />
43
+ <span>Save</span>
44
+ </Button>
45
+
46
+ <Title size="5" mt="4">
47
+ Sizes, variants, features, color
48
+ </Title>
49
+ {/* `size` sizes the Bulma container; `features` scales the glyph. */}
50
+ <Icon name="rocket" size="large" features="fa-2x" ariaLabel="Launch" />
51
+ {/* Font Awesome styles come from `variant`; brands require it. */}
52
+ <Icon name="bell" variant="regular" ariaLabel="Notifications" />
53
+ <Icon name="github" variant="brands" ariaLabel="GitHub" />
54
+ {/* Spin + color via features and the textColor helper. */}
55
+ <Icon
56
+ name="spinner"
57
+ features="fa-spin"
58
+ textColor="info"
59
+ ariaLabel="Loading"
60
+ />
61
+
62
+ <Title size="5" mt="4">
63
+ Multi-segment icon text
64
+ </Title>
65
+ <IconText
66
+ items={[
67
+ {
68
+ iconProps: { name: 'train', 'aria-hidden': 'true' },
69
+ text: 'Metro',
70
+ },
71
+ {
72
+ iconProps: { name: 'arrow-right', 'aria-hidden': 'true' },
73
+ text: 'Airport',
74
+ },
75
+ ]}
76
+ />
77
+ </Box>
78
+ </ConfigProvider>
79
+ );
80
+ }
@@ -0,0 +1,131 @@
1
+ # Icon libraries — setup, names, variants, troubleshooting
2
+
3
+ Facts an agent can act on for each of the five libraries `Icon` supports. The library ships no
4
+ icon fonts: the app must install the chosen library (the `npm create bestax` scaffold's
5
+ `--icon` flag does this; in an existing app, follow Setup below).
6
+
7
+ ## How `Icon` renders
8
+
9
+ - `fa` / `mdi` — an `<i>` with CSS classes (`fas fa-rocket`, `mdi mdi-rocket-launch`).
10
+ - `material-icons` / `material-symbols` — an `<i>` whose **text content** is the name
11
+ (a font ligature): `<i class="material-icons">rocket_launch</i>`. That's why these names are
12
+ snake_case — they are literal text, not class names.
13
+ - `ion` — the `<ion-icon>` web component: `<ion-icon name="rocket-outline" />`.
14
+
15
+ The resolution order for the library is `library` prop → `ConfigProvider iconLibrary` → `'fa'`.
16
+ Set it once on `ConfigProvider` and omit `library` everywhere else.
17
+
18
+ ## Font Awesome — `'fa'` (the default)
19
+
20
+ - **Setup:** `npm install @fortawesome/fontawesome-free` and
21
+ `import '@fortawesome/fontawesome-free/css/all.min.css';` once (e.g. `main.tsx`).
22
+ - **Scaffold flag:** `--icon fontawesome`.
23
+ - **Names:** kebab-case without the `fa-` prefix: `rocket`, `circle-check`, `magnifying-glass`.
24
+ A leading `fa-` in `name` is stripped automatically (so `fa-rocket` also works), but write
25
+ the bare name.
26
+ - **Variants** (`variant` → class): `solid` → `fas` (default), `regular` → `far`, `brands` →
27
+ `fab`, `light` → `fal`, `duotone` → `fad`, `thin` → `fat`. The free package includes only
28
+ solid, regular (partial), and brands — light/duotone/thin need a Font Awesome Pro kit.
29
+ - **Features:** Font Awesome utility classes — `'fa-lg'`, `'fa-2x'`…`'fa-10x'`, `'fa-spin'`,
30
+ `'fa-pulse'`, `'fa-border'`, `'fa-fw'`, `'fa-flip-horizontal'`, `'fa-rotate-90'`.
31
+ - Brand icons **require** `variant="brands"`: `<Icon name="github" variant="brands" />`.
32
+
33
+ ## Material Design Icons — `'mdi'`
34
+
35
+ - **Setup:** `npm install @mdi/font` and
36
+ `import '@mdi/font/css/materialdesignicons.min.css';`.
37
+ - **Scaffold flag:** `--icon mdi`.
38
+ - **Names:** kebab-case without the `mdi-` prefix: `account`, `rocket-launch`,
39
+ `home-outline`. A leading `mdi-` is stripped automatically. Outline/off styles are part of
40
+ the **name** (`home-outline`, `bell-off`), not a variant.
41
+ - **Variants:** none — `variant` is ignored for MDI.
42
+ - **Features:** MDI helpers (`'mdi-24px'`, `'mdi-48px'`, `'mdi-spin'`, `'mdi-rotate-90'`) or
43
+ Bulma text-size classes (`'is-size-3'`).
44
+
45
+ ## Ionicons — `'ion'` ⚠️ value is `ion`, not `ionicons`
46
+
47
+ - **Setup:** a CDN script pair in `index.html` (no npm package — it registers the
48
+ `<ion-icon>` web component):
49
+
50
+ ```html
51
+ <script
52
+ type="module"
53
+ src="https://unpkg.com/ionicons@8.0.13/dist/ionicons/ionicons.esm.js"
54
+ ></script>
55
+ <script
56
+ nomodule
57
+ src="https://unpkg.com/ionicons@8.0.13/dist/ionicons/ionicons.js"
58
+ ></script>
59
+ ```
60
+
61
+ - **Scaffold flag:** `--icon ionicons` — which maps to `iconLibrary="ion"`. Passing
62
+ `'ionicons'` as the `library`/`iconLibrary` value renders nothing.
63
+ - **Names:** kebab-case: `rocket`, `heart`, `settings`.
64
+ - **Variants:** `outline` and `sharp` — appended to the name (`variant="outline"` +
65
+ `name="heart"` renders `<ion-icon name="heart-outline">`). Omit for the filled default.
66
+ - **Features:** not applicable (web component, not classes); size the container with `size`
67
+ or style via CSS.
68
+
69
+ ## Google Material Icons — `'material-icons'`
70
+
71
+ - **Setup:** `npm install material-icons` and `import 'material-icons';`.
72
+ - **Scaffold flag:** `--icon material-icons`.
73
+ - **Names:** snake_case ligature text: `home`, `rocket_launch`, `shopping_cart`. A kebab-case
74
+ name will not match a ligature and renders as raw text.
75
+ - **Variants:** `filled` (default) / `outlined` / `round` / `sharp` — note **`round`**, not
76
+ `rounded`.
77
+ - **Features:** Bulma classes like `'is-size-1'` (the font scales with text size).
78
+
79
+ ## Material Symbols — `'material-symbols'`
80
+
81
+ - **Setup:** `npm install material-symbols` and `import 'material-symbols';`.
82
+ - **Scaffold flag:** `--icon material-symbols`.
83
+ - **Names:** snake_case ligature text, same as Material Icons: `rocket_launch`.
84
+ - **Variants:** `outlined` (default) / `rounded` / `sharp` — note **`rounded`** here vs
85
+ Material Icons' `round`.
86
+ - **Features:** Bulma classes like `'is-size-1'`.
87
+
88
+ ## One glyph, five names
89
+
90
+ | Glyph | fa | mdi | ion | material-icons / material-symbols |
91
+ | -------- | ------------------ | --------------- | ---------- | --------------------------------- |
92
+ | Rocket | `rocket` | `rocket-launch` | `rocket` | `rocket_launch` |
93
+ | Home | `house` / `home` | `home` | `home` | `home` |
94
+ | Settings | `gear` | `cog` | `settings` | `settings` |
95
+ | Search | `magnifying-glass` | `magnify` | `search` | `search` |
96
+
97
+ ## Blank icon? Check in this order
98
+
99
+ 1. **Library value** — `'ion'` not `'ionicons'`; the five valid values are `fa`, `mdi`,
100
+ `ion`, `material-icons`, `material-symbols`.
101
+ 2. **Name format for that library** — kebab vs snake_case (see the table above); for
102
+ material-* a wrong name renders as literal text instead of a glyph.
103
+ 3. **The library's CSS/script is actually loaded** — the import in `main.tsx` (or the
104
+ Ionicons scripts in `index.html`) must exist; bestax ships none of them.
105
+ 4. **Variant availability** — Font Awesome free has no `light`/`duotone`/`thin`; brand
106
+ glyphs need `variant="brands"`.
107
+
108
+ ## IconText
109
+
110
+ Pairs icon(s) with text inside a Bulma `icon-text` container. Single icon:
111
+
112
+ ```tsx
113
+ <IconText iconProps={{ name: 'check', 'aria-hidden': 'true' }}>Saved</IconText>
114
+ ```
115
+
116
+ Multiple segments via `items`:
117
+
118
+ ```tsx
119
+ <IconText
120
+ items={[
121
+ { iconProps: { name: 'train', 'aria-hidden': 'true' }, text: 'Metro' },
122
+ {
123
+ iconProps: { name: 'arrow-right', 'aria-hidden': 'true' },
124
+ text: 'Airport',
125
+ },
126
+ ]}
127
+ />
128
+ ```
129
+
130
+ Icons inside `IconText` sit next to their visible text — mark them decorative with
131
+ `aria-hidden` (see SKILL.md's accessibility rules).
@@ -36,8 +36,13 @@ Centered; a collection of items → Card grid. For mixed requests, pick the domi
36
36
  ## Approach
37
37
 
38
38
  - Compose pages from the shipped layout components — `Container`, `Section`, `Hero`, `Footer`,
39
- `Level`, `Columns`/`Column`, `Navbar`, `Menu`, `Card`. There is **no `Tile` component** — build
40
- grids with `Columns`/`Column`.
39
+ `Level`, `Columns`/`Column`, `Grid`/`Cell`, `Navbar`, `Menu`, `Card`. There is **no `Tile`
40
+ component**. For **uniform grids** (card grids, galleries — same-shaped items) prefer
41
+ `Grid`/`Cell`: CSS Grid gives equal-height cells for free (per row — each row's cells match
42
+ its tallest, same row-level behavior as the flex recipe). Use `Columns`/`Column` for
43
+ proportional or per-breakpoint column layouts — and when cards there must be equal height,
44
+ apply the flex recipe (`Column display="flex" flexDirection="column"` + `Card flexGrow="1"`;
45
+ `height: 100%` on the card doesn't help — the column's height is auto).
41
46
  - Rely on Bulma's responsive defaults: `Columns` sit side by side on tablet and up and stack on
42
47
  mobile. Add responsive `size*` props only to tune the breakpoints.
43
48
  - For a `fixed="top"` `Navbar`, add the `has-navbar-fixed-top` class to `<html>` so content is not
@@ -74,7 +79,9 @@ color }}`. Set the app-wide icon library once with `<ConfigProvider iconLibrary=
74
79
 
75
80
  - [ ] Map the request to one archetype; do not ask layout questions.
76
81
  - [ ] Wrap page content in `Container` (+ `Section` for vertical rhythm).
77
- - [ ] Use `Columns`/`Column` for side-by-side layout; rely on the mobile stack default.
82
+ - [ ] Use `Grid`/`Cell` for uniform grids (equal heights per row, free); `Columns`/`Column`
83
+ for proportional or per-breakpoint side-by-side layout — with the flex recipe when its
84
+ cards must match height.
78
85
  - [ ] For a fixed navbar, add `has-navbar-fixed-top` to `<html>`.
79
86
  - [ ] Do not use `Tile` — it is not shipped.
80
87
  - [ ] Style with helper props (`mt`/`p`, `textAlign`, `textColor`), not inline `style`.
@@ -1,6 +1,10 @@
1
1
  // Card grid / catalog page — a collection of similar items.
2
2
  // `<Columns isMultiline>` wraps cards onto new rows; the responsive column sizes
3
3
  // give 1 card per row on mobile, 2 on tablet, 3 on desktop.
4
+ // Equal heights: each Column is a flex container and its Card grows to fill
5
+ // it (flexGrow="1"), so short blurbs don't leave ragged card bottoms.
6
+ // (`height: 100%` on the card would NOT work — it resolves against auto
7
+ // height. For uniform grids, Grid/Cell gives equal heights for free.)
4
8
  import React from 'react';
5
9
  import {
6
10
  Section,
@@ -78,8 +82,11 @@ export default function CatalogPage() {
78
82
  sizeMobile="full"
79
83
  sizeTablet="half"
80
84
  sizeDesktop="one-third"
85
+ display="flex"
86
+ flexDirection="column"
81
87
  >
82
88
  <Card
89
+ flexGrow="1"
83
90
  image={product.image}
84
91
  imageAlt={product.name}
85
92
  header={product.name}
@@ -165,8 +165,11 @@ search results, "a grid of cards".
165
165
  sizeMobile="full"
166
166
  sizeTablet="half"
167
167
  sizeDesktop="one-third"
168
+ display="flex"
169
+ flexDirection="column"
168
170
  >
169
171
  <Card
172
+ flexGrow="1"
170
173
  image={item.image}
171
174
  header={item.name}
172
175
  footer={<span className="card-footer-item">{item.price}</span>}
@@ -183,6 +186,14 @@ search results, "a grid of cards".
183
186
  **Responsive:** `isMultiline` wraps cards onto new rows; the `size*` props set the per-row count —
184
187
  1 on mobile, 2 on tablet, 3 on desktop here. Change the fractions to change the column count.
185
188
 
189
+ **Equal heights:** the `display="flex" flexDirection="column"` on each `Column` plus
190
+ `flexGrow="1"` on the `Card` stretches every card to its row's height — without it, cards end
191
+ at their content and rows look ragged (`height: 100%` on the card doesn't help — it resolves
192
+ against the column's auto height). Alternatively
193
+ build the whole grid with `Grid`/`Cell` (`<Grid isFixed fixedColsMobile={1} fixedColsTablet={2}
194
+ fixedColsDesktop={3} gap={4}>`) — CSS Grid keeps cells equal-height for free; see the
195
+ `Grid / Cell` section in `layout-components.md`.
196
+
186
197
  ---
187
198
 
188
199
  ## Combining archetypes
@@ -12,6 +12,8 @@ import {
12
12
  Level,
13
13
  Columns,
14
14
  Column,
15
+ Grid,
16
+ Cell,
15
17
  Navbar,
16
18
  Menu,
17
19
  Card,
@@ -26,7 +28,9 @@ Every component also accepts the shared Bulma helper props (`m`/`p` spacing, `te
26
28
  > `textColor="grey"` / `bgColor="light"` not `style={{ color }}`. Spacing scale is `0`–`6` | `auto`
27
29
  > (`4` = 1rem). Reserve `style`/CSS vars only for values the design system doesn't tokenize.
28
30
 
29
- > **There is no `Tile` component.** Build grids and nested layouts with `Columns` / `Column`.
31
+ > **There is no `Tile` component.** For uniform grids (cards, galleries) use `Grid` / `Cell` —
32
+ > equal-height cells for free; for proportional or per-breakpoint layouts use
33
+ > `Columns` / `Column`.
30
34
 
31
35
  ## Container
32
36
 
@@ -127,6 +131,76 @@ type BulmaColumnSize =
127
131
  > Columns **stack on mobile** by default and go side-by-side at the tablet breakpoint and up.
128
132
  > Use the per-breakpoint `size*` props to control how many cells share a row at each width.
129
133
 
134
+ **Equal-height cards inside Columns** — columns are equal height, but a card inside one does
135
+ **not** stretch to fill it (and `height: 100%` on the card doesn't help — it resolves against
136
+ the column's auto height). Make the `Column` a flex container and let the card grow:
137
+
138
+ ```tsx
139
+ <Columns isMultiline>
140
+ {items.map(item => (
141
+ <Column
142
+ key={item.id}
143
+ sizeTablet="half"
144
+ sizeDesktop="one-third"
145
+ display="flex"
146
+ flexDirection="column"
147
+ >
148
+ <Card flexGrow="1">{item.blurb}</Card>
149
+ </Column>
150
+ ))}
151
+ </Columns>
152
+ ```
153
+
154
+ `display`, `flexDirection`, and `flexGrow` are helper props every component accepts;
155
+ `flexGrow` takes a string (`"1"`).
156
+
157
+ ## Grid / Cell
158
+
159
+ Bulma's CSS Grid. **Preferred for uniform grids** — same-shaped items in a repeating pattern
160
+ (card grids, galleries, dashboards): CSS Grid gives **equal-height cells for free** (per row —
161
+ each row's cells match its tallest), with no flex recipe needed. Reach for `Columns`/`Column` instead when you need proportional or
162
+ per-breakpoint column _sizes_ (a 2/3 + 1/3 split, different counts per breakpoint).
163
+
164
+ **Grid**
165
+
166
+ | Prop | Type |
167
+ | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
168
+ | `gap` / `columnGap` / `rowGap` | `0`–`8` (number or string, same scale as Columns' `gap`) |
169
+ | `minCol` | `1`–`32` (smart grid: min column width step, `is-col-min-X`) |
170
+ | `isFixed` | `boolean` (fixed column count instead of auto-fill) |
171
+ | `fixedCols` (+ `fixedColsMobile` / `fixedColsTablet` / `fixedColsDesktop` / `fixedColsWidescreen` / `fixedColsFullhd`) | `0`–`12` or `'auto'` (fixed grid only) |
172
+
173
+ **Cell**
174
+
175
+ | Prop | Type |
176
+ | ------------------------------------- | --------------------------------------- |
177
+ | `colStart` / `colFromEnd` / `colSpan` | `number` (manual column placement/span) |
178
+ | `rowStart` / `rowFromEnd` / `rowSpan` | `number` (manual row placement/span) |
179
+
180
+ By default the smart grid **auto-fills**: cells flow into as many columns as fit (tune the
181
+ minimum width with `minCol`). `isFixed` + `fixedCols*` pins an exact column count per
182
+ breakpoint instead.
183
+
184
+ ```tsx
185
+ // Uniform card grid — equal heights for free, responsive column count for free.
186
+ <Grid
187
+ isFixed
188
+ fixedColsMobile={1}
189
+ fixedColsTablet={2}
190
+ fixedColsDesktop={3}
191
+ gap={4}
192
+ >
193
+ {items.map(item => (
194
+ <Cell key={item.id}>
195
+ <Card header={item.name}>{item.blurb}</Card>
196
+ </Cell>
197
+ ))}
198
+ </Grid>
199
+ ```
200
+
201
+ > To stretch each card to its cell's full (equal) height, the same `display="flex"
202
+ flexDirection="column"` + `flexGrow="1"` pattern applies to `Cell` + `Card`.
203
+
130
204
  ## Navbar
131
205
 
132
206
  `<Navbar>` is the top bar. Subcomponents: `Navbar.Brand`, `Navbar.Item`, `Navbar.Link`,
@@ -150,6 +224,12 @@ flag to `Navbar.Menu active`. **A `fixed="top"` navbar requires `has-navbar-fixe
150
224
  document.documentElement.classList.add('has-navbar-fixed-top');
151
225
  ```
152
226
 
227
+ **Routing:** in a routed app, don't use `href="#"` — render items as the router's link
228
+ component. `Menu.Item as={Link} to="/x"` and `Navbar.Item as={Link} to="/x"` both compile
229
+ without casts (their props allow extra keys). Drive `active` from `useLocation().pathname`.
230
+ Full patterns (including Buttons that navigate and Next.js `href`): the docs guide at
231
+ https://bestax.io/docs/guides/features/routing.
232
+
153
233
  ## Menu
154
234
 
155
235
  `<Menu>` is a vertical sidebar menu. Subcomponents: `Menu.Label`, `Menu.List`, `Menu.Item`.