create-bestax 3.3.0 → 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.
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +10 -5
- package/dist/project-creator.d.ts.map +1 -1
- package/dist/project-creator.js +6 -1
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +6 -0
- package/package.json +1 -1
- package/templates/skills/bestax-icons/SKILL.md +78 -0
- package/templates/skills/bestax-icons/examples/icon-usage.tsx +80 -0
- package/templates/skills/bestax-icons/references/icon-libraries.md +131 -0
- package/templates/skills/bestax-layout-scaffold/SKILL.md +17 -3
- package/templates/skills/bestax-layout-scaffold/examples/card-grid.tsx +7 -0
- package/templates/skills/bestax-layout-scaffold/examples/landing.tsx +6 -1
- package/templates/skills/bestax-layout-scaffold/references/archetypes.md +17 -0
- package/templates/skills/bestax-layout-scaffold/references/layout-components.md +81 -1
- package/templates/skills/bestax-theming/SKILL.md +20 -0
- package/templates/skills/bestax-theming/references/css-variables.md +49 -0
package/dist/constants.d.ts
CHANGED
|
@@ -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
|
}
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,SAAS,EAAE,QAAQ,EAG/B,CAAC;AAEF,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AACpD,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,kBAAkB,QAAsB,CAAC;AAEtD,eAAO,MAAM,QAAQ
|
|
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;
|
|
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"}
|
package/dist/project-creator.js
CHANGED
|
@@ -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] =
|
|
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
|
package/dist/validators.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/validators.js
CHANGED
|
@@ -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
|
@@ -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`
|
|
40
|
-
grids
|
|
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
|
|
@@ -46,6 +51,13 @@ Centered; a collection of items → Card grid. For mixed requests, pick the domi
|
|
|
46
51
|
`textAlign="centered"`, and `textColor`/`bgColor` instead of `style={{ marginTop, textAlign,
|
|
47
52
|
color }}`. Set the app-wide icon library once with `<ConfigProvider iconLibrary="…">` at the root
|
|
48
53
|
rather than `library` on every `<Icon>`.
|
|
54
|
+
- **CTAs on a colored hero must stay legible in both schemes.** On a fixed-color surface
|
|
55
|
+
(`Hero color="primary"`, a dark banner), use **filled** buttons — `color="light"` or
|
|
56
|
+
`color="primary" isInverted` — never a thin `isOutlined` secondary: a light outline + light
|
|
57
|
+
label on a dark surface is low-contrast and gets worse under OS dark mode. And when the page's
|
|
58
|
+
design is single-mode (a fixed light or dark look), pin it at the root —
|
|
59
|
+
`<Theme isRoot colorMode="light">` — so a visitor's OS dark mode can't flip Bulma's text
|
|
60
|
+
colors out from under the fixed palette (details: the `bestax-theming` skill's contrast rules).
|
|
49
61
|
|
|
50
62
|
## References
|
|
51
63
|
|
|
@@ -67,7 +79,9 @@ color }}`. Set the app-wide icon library once with `<ConfigProvider iconLibrary=
|
|
|
67
79
|
|
|
68
80
|
- [ ] Map the request to one archetype; do not ask layout questions.
|
|
69
81
|
- [ ] Wrap page content in `Container` (+ `Section` for vertical rhythm).
|
|
70
|
-
- [ ] Use `
|
|
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.
|
|
71
85
|
- [ ] For a fixed navbar, add `has-navbar-fixed-top` to `<html>`.
|
|
72
86
|
- [ ] Do not use `Tile` — it is not shipped.
|
|
73
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}
|
|
@@ -33,11 +33,16 @@ export default function LandingPage() {
|
|
|
33
33
|
<SubTitle size="3">
|
|
34
34
|
The all-in-one platform for modern teams.
|
|
35
35
|
</SubTitle>
|
|
36
|
+
{/* Both CTAs are FILLED: on a fixed-color hero a thin outlined
|
|
37
|
+
button (light outline + light label) reads washed out, and
|
|
38
|
+
worse under OS dark mode. isInverted (no isOutlined) gives a
|
|
39
|
+
solid white button with primary text — high contrast in both
|
|
40
|
+
schemes. */}
|
|
36
41
|
<Buttons isCentered mt="5">
|
|
37
42
|
<Button color="light" size="large">
|
|
38
43
|
Get started
|
|
39
44
|
</Button>
|
|
40
|
-
<Button color="primary" isInverted
|
|
45
|
+
<Button color="primary" isInverted size="large">
|
|
41
46
|
Live demo
|
|
42
47
|
</Button>
|
|
43
48
|
</Buttons>
|
|
@@ -116,6 +116,12 @@ pricing page. The default for "build me a site/page".
|
|
|
116
116
|
**Responsive:** `Section`s already stack vertically. The feature `Columns` collapse to one feature
|
|
117
117
|
per row on mobile. Use `Hero size="large"` / `"fullheight"` for a taller hero.
|
|
118
118
|
|
|
119
|
+
**Hero CTAs:** on a colored hero use **filled** buttons only — `color="light"` for the primary
|
|
120
|
+
CTA and `color="primary" isInverted` (solid white, primary text) for a secondary. A thin
|
|
121
|
+
`isOutlined` button on a fixed-color surface is low-contrast and degrades further under OS dark
|
|
122
|
+
mode. Single-mode page designs should also pin the scheme at the root
|
|
123
|
+
(`<Theme isRoot colorMode="light">`) — see the `bestax-theming` skill's contrast rules.
|
|
124
|
+
|
|
119
125
|
---
|
|
120
126
|
|
|
121
127
|
## 3. Centered single-column
|
|
@@ -159,8 +165,11 @@ search results, "a grid of cards".
|
|
|
159
165
|
sizeMobile="full"
|
|
160
166
|
sizeTablet="half"
|
|
161
167
|
sizeDesktop="one-third"
|
|
168
|
+
display="flex"
|
|
169
|
+
flexDirection="column"
|
|
162
170
|
>
|
|
163
171
|
<Card
|
|
172
|
+
flexGrow="1"
|
|
164
173
|
image={item.image}
|
|
165
174
|
header={item.name}
|
|
166
175
|
footer={<span className="card-footer-item">{item.price}</span>}
|
|
@@ -177,6 +186,14 @@ search results, "a grid of cards".
|
|
|
177
186
|
**Responsive:** `isMultiline` wraps cards onto new rows; the `size*` props set the per-row count —
|
|
178
187
|
1 on mobile, 2 on tablet, 3 on desktop here. Change the fractions to change the column count.
|
|
179
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
|
+
|
|
180
197
|
---
|
|
181
198
|
|
|
182
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.**
|
|
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`.
|
|
@@ -35,6 +35,26 @@ For **dark mode**, pass `colorMode` to `Theme` (`'light' | 'dark' | 'system'`).
|
|
|
35
35
|
`Theme`; `'system'` follows the OS `prefers-color-scheme`. Drive it from state on the app-root
|
|
36
36
|
`Theme`: `<Theme isRoot colorMode={mode}>`.
|
|
37
37
|
|
|
38
|
+
## Contrast rules (dark mode is on by default)
|
|
39
|
+
|
|
40
|
+
When nothing sets a `data-theme` attribute (omitting `colorMode` preserves an existing one, but
|
|
41
|
+
apps that never configured it have none), Bulma follows the visitor's OS: `--bulma-text`,
|
|
42
|
+
`--bulma-scheme-main`, etc. flip on a dark-mode machine even if the design never intended a dark
|
|
43
|
+
theme. Custom fixed tokens (`--my-canvas: #f6f4ec`) do **not** flip — producing near-white Bulma
|
|
44
|
+
text on the author's fixed light background. Apply exactly one of these rules whenever custom
|
|
45
|
+
color tokens or fixed-color surfaces exist:
|
|
46
|
+
|
|
47
|
+
- **Single-mode design → pin the scheme.** `<Theme isRoot colorMode="light">` (or `"dark"`), so
|
|
48
|
+
an OS preference can never invert text out from under the fixed palette.
|
|
49
|
+
- **Both modes → no exposed fixed tokens.** Derive custom tokens from scheme variables
|
|
50
|
+
(`--my-canvas: var(--bulma-scheme-main)`) — or flip them yourself under **both** dark-mode
|
|
51
|
+
paths: `[data-theme='dark']` **and** `@media (prefers-color-scheme: dark)` scoped to
|
|
52
|
+
`:root:not([data-theme])`, since `colorMode="system"` removes the attribute (snippets in
|
|
53
|
+
`references/css-variables.md`).
|
|
54
|
+
- **Fixed-color surface → fixed-color content.** On a surface that never changes (a dark hero,
|
|
55
|
+
a brand banner), pin the content's colors too: solid/filled buttons and explicit text colors,
|
|
56
|
+
never scheme-derived defaults or thin outlines that depend on the flipping scheme.
|
|
57
|
+
|
|
38
58
|
Reach for the helper props (`color` / `textColor` / `bgColor` / `colorShade`, `textSize`,
|
|
39
59
|
`textWeight`, `fontFamily`) to apply themed colors and type to individual components.
|
|
40
60
|
|
|
@@ -147,6 +147,55 @@ Under dark mode Bulma flips the scheme/text/border/background lightness variable
|
|
|
147
147
|
`Theme isRoot` or `:root` still apply on top, because they set the hue/saturation/lightness
|
|
148
148
|
channels directly.
|
|
149
149
|
|
|
150
|
+
### The single-mode contrast trap
|
|
151
|
+
|
|
152
|
+
Because the OS preference applies whenever no `data-theme` attribute is set — the default state
|
|
153
|
+
of every app that never configured `colorMode` — a light-only design silently breaks for any
|
|
154
|
+
dark-mode visitor: Bulma's text goes near-white while author-defined fixed tokens stay light —
|
|
155
|
+
white text on cream. The failure is invisible unless the author's own OS is in dark mode.
|
|
156
|
+
|
|
157
|
+
**If the design is single-mode, pin the scheme** so text can't flip out from under the palette:
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
<Theme isRoot colorMode="light">
|
|
161
|
+
<App />
|
|
162
|
+
</Theme>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**If both modes are supported, never expose a fixed custom token to the flip** — derive it from
|
|
166
|
+
scheme variables, or flip it yourself:
|
|
167
|
+
|
|
168
|
+
```css
|
|
169
|
+
/* Preferred: track the scheme automatically. */
|
|
170
|
+
:root {
|
|
171
|
+
--my-canvas: var(--bulma-scheme-main);
|
|
172
|
+
--my-ink: var(--bulma-text);
|
|
173
|
+
}
|
|
174
|
+
/* Or, when custom values must be kept, provide the dark pair for BOTH
|
|
175
|
+
ways dark mode arrives — the explicit attribute (colorMode="dark")… */
|
|
176
|
+
[data-theme='dark'] {
|
|
177
|
+
--my-canvas: #14251b;
|
|
178
|
+
--my-ink: #eef3e7;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* …and the OS preference, which applies when no data-theme is set
|
|
182
|
+
(colorMode="system" removes the attribute): */
|
|
183
|
+
@media (prefers-color-scheme: dark) {
|
|
184
|
+
:root:not([data-theme]) {
|
|
185
|
+
--my-canvas: #14251b;
|
|
186
|
+
--my-ink: #eef3e7;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Deriving from scheme variables is preferred precisely because it covers both dark-mode paths
|
|
192
|
+
with no extra selector.
|
|
193
|
+
|
|
194
|
+
The same reasoning applies to **fixed-color surfaces** inside either kind of page (a dark hero,
|
|
195
|
+
a brand banner): content sitting on a surface that never flips must use pinned colors — filled
|
|
196
|
+
buttons and explicit text colors — not scheme-derived defaults (see the layout skill's hero CTA
|
|
197
|
+
rule).
|
|
198
|
+
|
|
150
199
|
## `Theme` props (named)
|
|
151
200
|
|
|
152
201
|
Color trios: `primaryH/primaryS/primaryL`, `linkH/linkS/linkL`, `infoH/S/L`, `successH/S/L`,
|