create-bestax 3.1.3 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -5
- package/dist/constants.d.ts +6 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +48 -3
- package/dist/project-creator.d.ts +2 -1
- package/dist/project-creator.d.ts.map +1 -1
- package/dist/project-creator.js +10 -12
- package/package.json +1 -1
- package/templates/skills/bestax-custom-component/SKILL.md +100 -329
- package/templates/skills/bestax-custom-component/examples/stat-card.tsx +101 -0
- package/templates/skills/bestax-custom-component/references/api.md +10 -1
- package/templates/skills/bestax-custom-component/references/component-catalog.md +7 -1
- package/templates/skills/bestax-custom-component/references/library-contributor.md +395 -0
- package/templates/skills/bestax-custom-component/references/patterns.md +5 -2
- package/templates/skills/bestax-theming/references/css-variables.md +27 -0
- package/templates/skills/bestax-theming/references/themeable-components.md +12 -10
- package/templates/vite/package.json +1 -1
- package/templates/vite-ts/package.json +1 -1
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bestax-custom-component
|
|
3
|
-
description: Build a
|
|
3
|
+
description: Build a custom React component in the bestax/Bulma style. In an app using @allxsmith/bestax-bulma — compose existing components, helper props, public hooks (useBulmaClasses, usePrefixedClassNames), and --bulma-* CSS variables. In the bestax monorepo — the full component pipeline (SCSS partial, stories, tests, docs, wiring). Use when creating a component beyond stock Bulma or extending one.
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# Building a custom bestax
|
|
7
|
+
# Building a custom component the bestax way
|
|
8
8
|
|
|
9
|
-
This skill teaches
|
|
10
|
-
|
|
11
|
-
component to `@allxsmith/bestax-bulma`, or refactor a component to match house style.
|
|
9
|
+
This skill teaches how to build a component that isn't in the library — composed from bestax
|
|
10
|
+
pieces in an app, or as a full library "extra" inside the bestax monorepo.
|
|
12
11
|
|
|
13
|
-
##
|
|
12
|
+
## Which context are you in?
|
|
14
13
|
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
-
|
|
14
|
+
- **The bestax monorepo** (the repo contains `bulma-ui/src/`) → follow
|
|
15
|
+
`references/library-contributor.md` instead of this file: five-file layout, SCSS partial,
|
|
16
|
+
stories, jest tests, docs page, wiring.
|
|
17
|
+
- **An app depending on `@allxsmith/bestax-bulma`** (e.g. scaffolded by `npm create bestax`) →
|
|
18
|
+
continue here. Everything below assumes public package imports and a plain Vite app.
|
|
19
19
|
|
|
20
20
|
For **form** components (Field/Control/Input/etc.) use the `bestax-form` skill instead.
|
|
21
21
|
|
|
@@ -30,359 +30,130 @@ call.
|
|
|
30
30
|
Where to look:
|
|
31
31
|
|
|
32
32
|
- `references/component-catalog.md` — **start here.** Every documented component with a one-line
|
|
33
|
-
purpose, grouped by category
|
|
34
|
-
|
|
35
|
-
- `bulma-ui/src/index.ts` — the full export list; check here for anything not yet documented (e.g.
|
|
36
|
-
raw `*Base` form variants) that the catalog omits.
|
|
37
|
-
- `docs/docs/api/{elements,components,form}/` — one doc page per shipped component (full props).
|
|
38
|
-
- Storybook titles — `Elements/*`, `Components/*`, `Form/*`.
|
|
33
|
+
purpose, grouped by category. Scan it for the name and its synonyms before anything else.
|
|
34
|
+
- https://bestax.io/docs/api — one doc page per shipped component (full props).
|
|
39
35
|
|
|
40
36
|
Then decide, and **surface the decision to the user**:
|
|
41
37
|
|
|
42
38
|
- **Exact / synonym match exists** → recommend using it. Don't build a duplicate. (E.g. a small
|
|
43
39
|
colored label/badge/chip → `Tag` / `Tags` already exist.)
|
|
44
|
-
- **Partial overlap** → prefer **composing
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
them.)
|
|
40
|
+
- **Partial overlap** → prefer **composing** the existing pieces inside your new component
|
|
41
|
+
rather than re-implementing them. (E.g. a "profile card" → there's no `ProfileCard`, but
|
|
42
|
+
`Card`, `Image`, `Title`, `SubTitle`, and `Content` exist; build `ProfileCard` to compose them.)
|
|
48
43
|
- **Genuine gap** → build the new component using the pattern below.
|
|
49
44
|
|
|
50
45
|
State plainly which case applies before writing code, e.g. _"`Tag` already covers a colored
|
|
51
46
|
label — use that instead"_ or _"No `ProfileCard` exists; I'll build one composing the existing
|
|
52
47
|
`Card`/`Image`/`Title` elements."_
|
|
53
48
|
|
|
54
|
-
##
|
|
49
|
+
## Composition first
|
|
55
50
|
|
|
56
|
-
|
|
57
|
-
`
|
|
51
|
+
Build from existing components before writing any CSS: `Box`, `Card`, `Title`, `SubTitle`,
|
|
52
|
+
`Icon`, `Block`, `Content`, `Tag`, plus the Bulma helper props every component accepts (spacing,
|
|
53
|
+
color, typography, flexbox). Most "custom components" are a composition function — zero new
|
|
54
|
+
styles. See `examples/stat-card.tsx` for a complete worked example.
|
|
58
55
|
|
|
59
|
-
|
|
60
|
-
bulma-ui/src/components/MyComponent.tsx # React + TS component
|
|
61
|
-
bulma-ui/src/components/MyComponent.stories.tsx # Storybook stories
|
|
62
|
-
bulma-ui/src/components/__tests__/MyComponent.test.tsx # Jest + RTL tests
|
|
63
|
-
bulma-ui/src/scss/components/_mycomponent.scss # SCSS partial
|
|
64
|
-
docs/docs/api/components/mycomponent.md # Docusaurus docs page
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Then wire two index files (see **Wiring & build**).
|
|
56
|
+
## The component spine
|
|
68
57
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
Components use `forwardRef`, accept Bulma helper props via `BulmaClassesProps`, run them
|
|
72
|
-
through `useBulmaClasses`, build their own classes with `usePrefixedClassNames`, and merge
|
|
73
|
-
everything with `classNames`. Spread `rest` (the non-helper props) onto the DOM node.
|
|
58
|
+
Same shape the library itself uses, with all imports from the package. File at
|
|
59
|
+
`src/components/MyComponent.tsx`:
|
|
74
60
|
|
|
75
61
|
```tsx
|
|
76
|
-
import React
|
|
77
|
-
import {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
* Props for the MyComponent component.
|
|
85
|
-
*
|
|
86
|
-
* @property {MyComponentColor} [color] - Bulma color modifier.
|
|
87
|
-
* @property {'small' | 'medium' | 'large'} [size] - Size modifier.
|
|
88
|
-
* @property {boolean} [isActive] - Whether the component is active.
|
|
89
|
-
*/
|
|
62
|
+
import type React from 'react';
|
|
63
|
+
import {
|
|
64
|
+
classNames,
|
|
65
|
+
usePrefixedClassNames,
|
|
66
|
+
useBulmaClasses,
|
|
67
|
+
type BulmaClassesProps,
|
|
68
|
+
} from '@allxsmith/bestax-bulma';
|
|
69
|
+
|
|
90
70
|
export interface MyComponentProps
|
|
91
71
|
extends
|
|
92
72
|
Omit<React.HTMLAttributes<HTMLDivElement>, 'color'>,
|
|
93
73
|
Omit<BulmaClassesProps, 'color'> {
|
|
94
|
-
color?:
|
|
95
|
-
size?: 'small' | 'medium' | 'large';
|
|
96
|
-
isActive?: boolean;
|
|
74
|
+
color?: 'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger';
|
|
97
75
|
}
|
|
98
76
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// 3. Merge: own classes + helper classes + caller className.
|
|
118
|
-
const combined = classNames(mainClasses, bulmaHelperClasses, className);
|
|
119
|
-
|
|
120
|
-
return (
|
|
121
|
-
<div ref={ref} className={combined} {...rest}>
|
|
122
|
-
{children}
|
|
123
|
-
</div>
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
MyComponent.displayName = 'MyComponent';
|
|
129
|
-
|
|
130
|
-
export default MyComponent;
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
Rules that keep components consistent:
|
|
134
|
-
|
|
135
|
-
- **Always `Omit<…, 'color'>`** from both `HTMLAttributes` and `BulmaClassesProps` when the
|
|
136
|
-
component exposes its own typed `color`, so the native/helper `color` doesn't collide.
|
|
137
|
-
- **Never hand-build class strings.** Use `usePrefixedClassNames(base, conditionalMap)` so the
|
|
138
|
-
optional `classPrefix` from `ConfigProvider` is honored, then `classNames(...)` to merge.
|
|
139
|
-
- **Spread `rest`, not `props`**, onto the DOM node — `useBulmaClasses` has already stripped the
|
|
140
|
-
helper props out of `rest`, so they don't leak to the DOM as invalid attributes.
|
|
141
|
-
- **Set `displayName`** on `forwardRef` components (needed for tests and Storybook autodocs).
|
|
142
|
-
- **Element sizing uses an inline `'small' | 'medium' | 'large'` union**, mapped to `is-small` /
|
|
143
|
-
`is-medium` / `is-large` (see `Tabs.tsx`, `Control.tsx`). Do **not** reach for the `validSizes`
|
|
144
|
-
constant — that one is `'0'…'6' | 'auto'` and exists for **spacing** helpers, not element size.
|
|
145
|
-
- **Format before you lint.** The repo enforces Prettier and ESLint fails on unformatted code.
|
|
146
|
-
Run `pnpm exec prettier --write` on your new files (or `pnpm format` from the repo root) before
|
|
147
|
-
`pnpm lint`. Copy snippets as a starting point, then let Prettier normalize them.
|
|
148
|
-
|
|
149
|
-
See `references/api.md` for the full helper API and `references/patterns.md` for the complete
|
|
150
|
-
Dialog walkthrough.
|
|
151
|
-
|
|
152
|
-
## SCSS pattern (required)
|
|
153
|
-
|
|
154
|
-
This is the library's house convention — **the Bulma v1 CSS-variable pattern**. Do not write
|
|
155
|
-
plain hard-coded CSS or homebrew `--mycomponent-*` variables. Import Bulma's utilities, declare
|
|
156
|
-
SCSS vars with `!default`, register them as `--bulma-*` custom properties on the root selector
|
|
157
|
-
with `cv.register-vars`, then consume them with `cv.getVar`. Prefix every selector with
|
|
158
|
-
`iv.$class-prefix`.
|
|
159
|
-
|
|
160
|
-
```scss
|
|
161
|
-
// bulma-ui/src/scss/components/_mycomponent.scss
|
|
162
|
-
@use 'bulma/sass/utilities/initial-variables' as iv;
|
|
163
|
-
@use 'bulma/sass/utilities/css-variables' as cv;
|
|
164
|
-
|
|
165
|
-
// 1. SCSS variables, overridable, referencing Bulma vars via cv.getVar.
|
|
166
|
-
$mycomponent-radius: cv.getVar('radius') !default;
|
|
167
|
-
$mycomponent-background: cv.getVar('scheme-main') !default;
|
|
168
|
-
$mycomponent-color: cv.getVar('text') !default;
|
|
169
|
-
$mycomponent-padding: 1rem !default;
|
|
170
|
-
|
|
171
|
-
// 2. Register them as runtime --bulma-* custom properties on the root selector.
|
|
172
|
-
.#{iv.$class-prefix}mycomponent {
|
|
173
|
-
@include cv.register-vars(
|
|
174
|
-
(
|
|
175
|
-
'mycomponent-radius': #{$mycomponent-radius},
|
|
176
|
-
'mycomponent-background': #{$mycomponent-background},
|
|
177
|
-
'mycomponent-color': #{$mycomponent-color},
|
|
178
|
-
'mycomponent-padding': #{$mycomponent-padding},
|
|
179
|
-
)
|
|
77
|
+
export function MyComponent({
|
|
78
|
+
color,
|
|
79
|
+
className,
|
|
80
|
+
children,
|
|
81
|
+
...props
|
|
82
|
+
}: MyComponentProps) {
|
|
83
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
84
|
+
const mainClasses = usePrefixedClassNames('mycomponent', {
|
|
85
|
+
[`is-${color}`]: !!color,
|
|
86
|
+
});
|
|
87
|
+
return (
|
|
88
|
+
<div
|
|
89
|
+
className={classNames(mainClasses, bulmaHelperClasses, className)}
|
|
90
|
+
{...rest}
|
|
91
|
+
>
|
|
92
|
+
{children}
|
|
93
|
+
</div>
|
|
180
94
|
);
|
|
181
95
|
}
|
|
96
|
+
```
|
|
182
97
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
98
|
+
This gives your component the full Bulma helper-prop surface (`m`, `p`, `textAlign`, …) for
|
|
99
|
+
free. `references/api.md` documents the helpers.
|
|
100
|
+
|
|
101
|
+
## Styling ladder — use the lowest rung that works
|
|
102
|
+
|
|
103
|
+
**Rung 1 — helper props only (default).** House rules: never `style={{}}`. Layout with
|
|
104
|
+
`Block`/`Box` and `display="flex"`, `flexDirection`, `alignItems`, `justifyContent`. There is
|
|
105
|
+
**no `gap` helper** — space children with `m*`/`p*` margins instead.
|
|
106
|
+
|
|
107
|
+
**Rung 2 — a plain CSS file**, scoped under the component's class, consuming `--bulma-*`
|
|
108
|
+
variables — never literal colors, so `Theme` and dark mode keep working:
|
|
109
|
+
|
|
110
|
+
```css
|
|
111
|
+
/* src/components/MyComponent.css — import from the .tsx file */
|
|
112
|
+
.mycomponent {
|
|
113
|
+
/* component-scoped custom props, initialized from Bulma tokens:
|
|
114
|
+
any ancestor (or Theme) can re-theme by overriding them */
|
|
115
|
+
--mycomponent-radius: var(--bulma-radius);
|
|
116
|
+
--mycomponent-accent: var(--bulma-primary);
|
|
117
|
+
border-radius: var(--mycomponent-radius);
|
|
118
|
+
border: 1px solid var(--bulma-border);
|
|
119
|
+
background: var(--bulma-scheme-main);
|
|
120
|
+
color: var(--bulma-text);
|
|
195
121
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
@media (prefers-reduced-motion: reduce) {
|
|
199
|
-
.#{iv.$class-prefix}mycomponent {
|
|
200
|
-
animation: none;
|
|
201
|
-
}
|
|
122
|
+
.mycomponent .mycomponent-value {
|
|
123
|
+
color: var(--mycomponent-accent);
|
|
202
124
|
}
|
|
203
125
|
```
|
|
204
126
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
The canonical reference file is `bulma-ui/src/scss/components/_dialog.scss`.
|
|
210
|
-
|
|
211
|
-
## Stories
|
|
212
|
-
|
|
213
|
-
`MyComponent.stories.tsx` beside the component. Use `tags: ['autodocs']` so the JSDoc becomes
|
|
214
|
-
the docs page, declare `argTypes`, and write one named `function`-style render per variant.
|
|
215
|
-
|
|
216
|
-
```tsx
|
|
217
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
218
|
-
import { MyComponent } from './MyComponent';
|
|
219
|
-
|
|
220
|
-
const meta: Meta<typeof MyComponent> = {
|
|
221
|
-
title: 'Components/MyComponent',
|
|
222
|
-
component: MyComponent,
|
|
223
|
-
tags: ['autodocs'],
|
|
224
|
-
argTypes: {
|
|
225
|
-
color: {
|
|
226
|
-
control: 'select',
|
|
227
|
-
options: ['primary', 'link', 'info', 'success', 'warning', 'danger'],
|
|
228
|
-
},
|
|
229
|
-
isActive: { control: 'boolean' },
|
|
230
|
-
},
|
|
231
|
-
};
|
|
232
|
-
export default meta;
|
|
233
|
-
type Story = StoryObj<typeof MyComponent>;
|
|
234
|
-
|
|
235
|
-
export const Default: Story = {
|
|
236
|
-
render: function DefaultExample() {
|
|
237
|
-
return <MyComponent>Default</MyComponent>;
|
|
238
|
-
},
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
export const Colors: Story = {
|
|
242
|
-
render: function ColorsExample() {
|
|
243
|
-
return (
|
|
244
|
-
<>
|
|
245
|
-
<MyComponent color="primary">Primary</MyComponent>
|
|
246
|
-
<MyComponent color="danger">Danger</MyComponent>
|
|
247
|
-
</>
|
|
248
|
-
);
|
|
249
|
-
},
|
|
250
|
-
};
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
## Tests
|
|
254
|
-
|
|
255
|
-
`__tests__/MyComponent.test.tsx`, Jest + `@testing-library/react`. Cover render, each prop →
|
|
256
|
-
class mapping, the helper-prop passthrough, ref forwarding, and any interaction/a11y.
|
|
257
|
-
|
|
258
|
-
```tsx
|
|
259
|
-
import { render, screen } from '@testing-library/react';
|
|
260
|
-
import { createRef } from 'react';
|
|
261
|
-
import { MyComponent } from '../MyComponent';
|
|
262
|
-
|
|
263
|
-
describe('MyComponent', () => {
|
|
264
|
-
it('renders children', () => {
|
|
265
|
-
render(<MyComponent>Hello</MyComponent>);
|
|
266
|
-
expect(screen.getByText('Hello')).toBeInTheDocument();
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it('applies the color modifier', () => {
|
|
270
|
-
render(<MyComponent color="primary">x</MyComponent>);
|
|
271
|
-
expect(screen.getByText('x')).toHaveClass('mycomponent', 'is-primary');
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
it('passes Bulma helper props through', () => {
|
|
275
|
-
render(<MyComponent m="3">x</MyComponent>);
|
|
276
|
-
expect(screen.getByText('x')).toHaveClass('m-3');
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
it('forwards the ref', () => {
|
|
280
|
-
const ref = createRef<HTMLDivElement>();
|
|
281
|
-
render(<MyComponent ref={ref}>x</MyComponent>);
|
|
282
|
-
expect(ref.current).toBeInstanceOf(HTMLDivElement);
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
## Docs page
|
|
288
|
-
|
|
289
|
-
`docs/docs/api/components/mycomponent.md` — Overview, Import, a Props table, and `Usage` with
|
|
290
|
-
live examples. Live code blocks use the ` ```tsx live ` fence (Docusaurus live-codeblock).
|
|
291
|
-
|
|
292
|
-
````md
|
|
293
|
-
---
|
|
294
|
-
title: MyComponent
|
|
295
|
-
sidebar_label: MyComponent
|
|
296
|
-
---
|
|
297
|
-
|
|
298
|
-
# MyComponent
|
|
299
|
-
|
|
300
|
-
## Overview
|
|
301
|
-
|
|
302
|
-
Short description of the component.
|
|
303
|
-
|
|
304
|
-
## Import
|
|
305
|
-
|
|
306
|
-
```tsx
|
|
307
|
-
import { MyComponent } from '@allxsmith/bestax-bulma';
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
## Props
|
|
311
|
-
|
|
312
|
-
| Prop | Type | Default | Description |
|
|
313
|
-
| ---------- | ---------------------------- | ------- | --------------------- |
|
|
314
|
-
| `color` | `'primary' \| 'link' \| ...` | — | Bulma color modifier. |
|
|
315
|
-
| `isActive` | `boolean` | `false` | Active state. |
|
|
316
|
-
|
|
317
|
-
## Usage
|
|
127
|
+
Caveat: with the prefixed CSS flavor / `ConfigProvider classPrefix`, `usePrefixedClassNames`
|
|
128
|
+
prefixes your classes too — your CSS selectors must match (or build them with plain
|
|
129
|
+
`classNames` instead).
|
|
318
130
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
````
|
|
325
|
-
|
|
326
|
-
> Note: the Docusaurus docs load the **built** dist CSS. After SCSS changes, run
|
|
327
|
-
> `cd bulma-ui && pnpm build` before the new styles show up in the docs site (Storybook
|
|
328
|
-
> compiles SCSS live and does not need this).
|
|
329
|
-
|
|
330
|
-
## Wiring & build
|
|
331
|
-
|
|
332
|
-
Two index files must be updated or the component won't ship:
|
|
333
|
-
|
|
334
|
-
1. **Package export** — add to `bulma-ui/src/index.ts`, in the **components** group (the file
|
|
335
|
-
groups exports by directory — keep yours next to the other `./components/*` lines):
|
|
336
|
-
```ts
|
|
337
|
-
export * from './components/MyComponent';
|
|
338
|
-
```
|
|
339
|
-
2. **SCSS bundle** — add to `bulma-ui/src/scss/components/_index.scss`:
|
|
340
|
-
```scss
|
|
341
|
-
@use 'mycomponent';
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
Then build and verify:
|
|
345
|
-
|
|
346
|
-
```sh
|
|
347
|
-
cd bulma-ui
|
|
348
|
-
pnpm exec prettier --write src/components/MyComponent.tsx src/scss/components/_mycomponent.scss
|
|
349
|
-
pnpm lint
|
|
350
|
-
pnpm test
|
|
351
|
-
pnpm build # compiles JS + the bestax/extras CSS bundles
|
|
352
|
-
```
|
|
131
|
+
**Rung 3 — real Sass (optional).** `npm i -D sass` — nothing else; Vite compiles imported
|
|
132
|
+
`.scss` zero-config, and `bulma` is resolvable because it's a runtime dependency of
|
|
133
|
+
bestax-bulma. Then the full `register-vars`/`getVar` pattern from
|
|
134
|
+
`references/library-contributor.md` works in-app. Prefixed flavor:
|
|
135
|
+
`@use 'bulma/sass/utilities/initial-variables' with ($class-prefix: 'bestax-')`.
|
|
353
136
|
|
|
354
|
-
##
|
|
137
|
+
## Verify in the browser
|
|
355
138
|
|
|
356
|
-
Types
|
|
357
|
-
|
|
358
|
-
|
|
139
|
+
Types don't see layout. Run `npm run dev`, render the component, and actually look at it:
|
|
140
|
+
vertical centering of inline text (use `display="flex" alignItems="center"`, not line-height
|
|
141
|
+
hacks), balanced padding, nothing clipping, every color/size variant, and **dark mode**
|
|
142
|
+
legibility. Fix what you see, then re-check.
|
|
359
143
|
|
|
360
|
-
|
|
361
|
-
2. Open the component and inspect it. If a browser-automation tool (claude-in-chrome, Playwright)
|
|
362
|
-
is available, drive the browser and screenshot each variant; otherwise open it yourself and
|
|
363
|
-
eyeball it.
|
|
364
|
-
3. Check the usual offenders:
|
|
365
|
-
- **Vertical centering of inline text** — `display: inline-block` + `line-height: 1` makes
|
|
366
|
-
text sit low. For chips/labels/buttons use `display: inline-flex; align-items: center;
|
|
367
|
-
justify-content: center;` with a normal `line-height` (Bulma's `Tag` is the reference).
|
|
368
|
-
- Padding/gaps look balanced; nothing clips or overflows.
|
|
369
|
-
- Every color/size variant renders; dark mode is legible.
|
|
144
|
+
## Tests and stories in an app
|
|
370
145
|
|
|
371
|
-
|
|
146
|
+
The scaffolded app has **no test runner and no Storybook** — do not install or scaffold them
|
|
147
|
+
unasked. If the app already has vitest/jest + Testing Library, write the four test shapes:
|
|
148
|
+
render, prop→class mapping, helper-prop passthrough (`m="3"` → `m-3`), and the
|
|
149
|
+
`ConfigProvider classPrefix` case if the app uses a prefix.
|
|
372
150
|
|
|
373
151
|
## Checklist
|
|
374
152
|
|
|
375
|
-
- [ ]
|
|
376
|
-
|
|
377
|
-
- [ ]
|
|
378
|
-
|
|
379
|
-
- [ ]
|
|
380
|
-
|
|
381
|
-
- [ ]
|
|
382
|
-
- [ ] `__tests__/MyComponent.test.tsx` — render, prop→class, helper passthrough, ref.
|
|
383
|
-
- [ ] `docs/docs/api/components/mycomponent.md` — Overview / Import / Props / `tsx live`.
|
|
384
|
-
- [ ] `src/index.ts` exports the component (in the `./components/*` group).
|
|
385
|
-
- [ ] `scss/components/_index.scss` `@use`s the partial.
|
|
386
|
-
- [ ] Prettier-formatted, then `pnpm lint && pnpm test && pnpm build` all pass.
|
|
387
|
-
- [ ] **Rendered and visually inspected in a browser** — centering/spacing/variants all look
|
|
388
|
-
right (not just green tests).
|
|
153
|
+
- [ ] Inventory checked (catalog + bestax.io/docs/api) and the decision surfaced to the user.
|
|
154
|
+
- [ ] All imports from `@allxsmith/bestax-bulma` (no deep/internal paths).
|
|
155
|
+
- [ ] Composition first — existing components + helper props before any CSS.
|
|
156
|
+
- [ ] No inline `style={{}}` anywhere.
|
|
157
|
+
- [ ] Lowest sufficient ladder rung (helper props → scoped CSS vars → Sass).
|
|
158
|
+
- [ ] All colors/radii derived from `--bulma-*` variables — no literals.
|
|
159
|
+
- [ ] Renders correctly via `npm run dev`, including dark mode.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// StatCard — the app-side worked example for the bestax-custom-component skill.
|
|
2
|
+
// (The library-contributor counterpart is Dialog, in references/patterns.md.)
|
|
3
|
+
//
|
|
4
|
+
// Context: an app depending on @allxsmith/bestax-bulma (e.g. `npm create bestax`).
|
|
5
|
+
// Everything imports from the package; no monorepo wiring, no Sass pipeline.
|
|
6
|
+
//
|
|
7
|
+
// It demonstrates the two lowest rungs of the styling ladder:
|
|
8
|
+
// Rung 1 — composition + helper props only (StatCard): Box/Title/Icon plus
|
|
9
|
+
// flexbox and spacing helper props. No CSS written at all.
|
|
10
|
+
// Rung 2 — a small scoped CSS file consuming --bulma-* variables (the
|
|
11
|
+
// commented block at the bottom) for the one thing helper props
|
|
12
|
+
// can't do (an accent border), keeping Theme + dark mode working.
|
|
13
|
+
import type React from 'react';
|
|
14
|
+
import {
|
|
15
|
+
Box,
|
|
16
|
+
Title,
|
|
17
|
+
Icon,
|
|
18
|
+
classNames,
|
|
19
|
+
usePrefixedClassNames,
|
|
20
|
+
useBulmaClasses,
|
|
21
|
+
type BulmaClassesProps,
|
|
22
|
+
} from '@allxsmith/bestax-bulma';
|
|
23
|
+
|
|
24
|
+
export interface StatCardProps
|
|
25
|
+
extends
|
|
26
|
+
Omit<React.HTMLAttributes<HTMLDivElement>, 'color'>,
|
|
27
|
+
Omit<BulmaClassesProps, 'color'> {
|
|
28
|
+
/** Metric label, e.g. "Active users". */
|
|
29
|
+
label: string;
|
|
30
|
+
/** The headline value, e.g. "12,481". */
|
|
31
|
+
value: string;
|
|
32
|
+
/** Icon name (Font Awesome by default), e.g. "users". */
|
|
33
|
+
icon?: string;
|
|
34
|
+
/** Bulma color for the icon + accent. */
|
|
35
|
+
color?: 'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function StatCard({
|
|
39
|
+
label,
|
|
40
|
+
value,
|
|
41
|
+
icon,
|
|
42
|
+
color = 'primary',
|
|
43
|
+
className,
|
|
44
|
+
...props
|
|
45
|
+
}: StatCardProps) {
|
|
46
|
+
// The library's own spine, via public exports: helper props in, classes out.
|
|
47
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
48
|
+
const mainClasses = usePrefixedClassNames('statcard', {
|
|
49
|
+
[`is-${color}`]: !!color,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<Box
|
|
54
|
+
className={classNames(mainClasses, bulmaHelperClasses, className)}
|
|
55
|
+
// Rung 1: layout entirely with helper props — no style={{}}, no CSS.
|
|
56
|
+
display="flex"
|
|
57
|
+
alignItems="center"
|
|
58
|
+
p="4"
|
|
59
|
+
{...rest}
|
|
60
|
+
>
|
|
61
|
+
{icon && (
|
|
62
|
+
<Icon
|
|
63
|
+
name={icon}
|
|
64
|
+
size="large"
|
|
65
|
+
textColor={color}
|
|
66
|
+
mr="4"
|
|
67
|
+
ariaLabel={`${label} icon`}
|
|
68
|
+
/>
|
|
69
|
+
)}
|
|
70
|
+
{/* No `gap` helper exists — space siblings with margin props (mr above). */}
|
|
71
|
+
<div>
|
|
72
|
+
{/* as="p": the sizes are visual scale, not document structure — a bare
|
|
73
|
+
<Title size> renders a heading and breaks the page outline. */}
|
|
74
|
+
<Title as="p" size="6" textColor="grey" mb="1">
|
|
75
|
+
{label}
|
|
76
|
+
</Title>
|
|
77
|
+
<Title as="p" size="3" mb="0">
|
|
78
|
+
{value}
|
|
79
|
+
</Title>
|
|
80
|
+
</div>
|
|
81
|
+
</Box>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Rung 2 (optional) — src/components/StatCard.css, imported from this file:
|
|
86
|
+
//
|
|
87
|
+
// .statcard {
|
|
88
|
+
// /* Component-scoped custom props initialized from Bulma tokens, so any
|
|
89
|
+
// ancestor (or <Theme>) can re-theme the card by overriding them. */
|
|
90
|
+
// --statcard-accent: var(--bulma-primary);
|
|
91
|
+
// --statcard-radius: var(--bulma-radius);
|
|
92
|
+
// border-left: 0.25rem solid var(--statcard-accent);
|
|
93
|
+
// border-radius: var(--statcard-radius);
|
|
94
|
+
// }
|
|
95
|
+
// .statcard.is-success { --statcard-accent: var(--bulma-success); }
|
|
96
|
+
// .statcard.is-danger { --statcard-accent: var(--bulma-danger); }
|
|
97
|
+
//
|
|
98
|
+
// Only --bulma-*-derived values — never literal colors — so dark mode and
|
|
99
|
+
// Theme overrides keep working. Caveat: if the app uses the prefixed CSS
|
|
100
|
+
// flavor / ConfigProvider classPrefix, usePrefixedClassNames renders
|
|
101
|
+
// `bestax-statcard`; adjust the selectors (or use plain classNames).
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# Reference: helper APIs for building components
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Everything below is public API. Where to import from depends on your context:
|
|
4
|
+
|
|
5
|
+
| Context | Import from |
|
|
6
|
+
| --------------------------------------------- | ------------------------------------------------ |
|
|
7
|
+
| An app depending on `@allxsmith/bestax-bulma` | `'@allxsmith/bestax-bulma'` |
|
|
8
|
+
| Inside the bestax monorepo (`bulma-ui/src/`) | Relative paths — `'../helpers/classNames'`, etc. |
|
|
4
9
|
|
|
5
10
|
## `useBulmaClasses(props)` — `helpers/useBulmaClasses.tsx`
|
|
6
11
|
|
|
@@ -72,6 +77,10 @@ hard-coding values.
|
|
|
72
77
|
@use 'bulma/sass/utilities/css-variables' as cv; // cv.getVar, cv.register-vars
|
|
73
78
|
```
|
|
74
79
|
|
|
80
|
+
In an app these work too (styling-ladder rung 3 in `SKILL.md`): `npm i -D sass` and Vite
|
|
81
|
+
compiles imported `.scss` zero-config — `bulma` resolves because it's a runtime dependency of
|
|
82
|
+
bestax-bulma.
|
|
83
|
+
|
|
75
84
|
- `iv.$class-prefix` — the configurable class prefix; prepend to every selector.
|
|
76
85
|
- `cv.getVar("name")` — emits `var(--bulma-name)`; use for both Bulma vars (`"primary"`,
|
|
77
86
|
`"radius"`, `"scheme-main"`, `"text"`) and your own registered vars.
|
|
@@ -19,7 +19,7 @@ instead of hand-writing markup.
|
|
|
19
19
|
- Raw `*Base` form exports (`InputBase`, `SelectBase`, `TextAreaBase`, …) are
|
|
20
20
|
escape-hatch variants of the convenience wrappers above them; see the Form docs.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
87 documented components. Generated from the API docs — every exported
|
|
23
23
|
component is guaranteed to appear (the generator fails if one lacks an API page).
|
|
24
24
|
|
|
25
25
|
## Elements
|
|
@@ -57,6 +57,9 @@ component is guaranteed to appear (the generator fails if one lacks an API page)
|
|
|
57
57
|
|
|
58
58
|
## Components
|
|
59
59
|
|
|
60
|
+
- [Avatar](https://bestax.io/docs/api/components/avatar) — The `Avatar` component represents a person or entity as a compact image.
|
|
61
|
+
- [Avatars](https://bestax.io/docs/api/components/avatars) — The `Avatars` component renders an overlapping/stacked group of `Avatar`s, the "members" list pattern.
|
|
62
|
+
- [Badge](https://bestax.io/docs/api/components/badge) — The `Badge` component is a small status/count indicator overlaid on the corner of another element, or rendered standalone.
|
|
60
63
|
- [Breadcrumb](https://bestax.io/docs/api/components/breadcrumb) — The `Breadcrumb` component renders a Bulma-styled breadcrumb navigation.
|
|
61
64
|
- [Card](https://bestax.io/docs/api/components/card) — The `Card` component renders a Bulma-styled card with optional header, image, content, and footer.
|
|
62
65
|
- [Carousel](https://bestax.io/docs/api/components/carousel) — The `Carousel` component provides an image/content slider with navigation arrows and indicators.
|
|
@@ -70,6 +73,7 @@ component is guaranteed to appear (the generator fails if one lacks an API page)
|
|
|
70
73
|
- [Navbar](https://bestax.io/docs/api/components/navbar) — The `Navbar` component implements Bulma's powerful, responsive navigation bar for your Bulma React UI.
|
|
71
74
|
- [Pagination](https://bestax.io/docs/api/components/pagination) — The `Pagination` component provides a flexible, composable Bulma pagination navigation for your Bulma React UI.
|
|
72
75
|
- [Panel](https://bestax.io/docs/api/components/panel) — The `Panel` component implements Bulma's versatile panel block for React.
|
|
76
|
+
- [Reveal](https://bestax.io/docs/api/components/reveal) — The `Reveal` component animates its content into view as it scrolls into the viewport, backed by `IntersectionObserver`.
|
|
73
77
|
- [Sidebar](https://bestax.io/docs/api/components/sidebar) — The `Sidebar` component provides a slide-out navigation panel that appears from the left or right side of the screen.
|
|
74
78
|
- [Steps](https://bestax.io/docs/api/components/steps) — The `Steps` component provides a multi-step progress indicator for wizard flows, checkout processes, or any multi-step workflow.
|
|
75
79
|
- [Tabs](https://bestax.io/docs/api/components/tabs) — The `Tabs` component provides flexible and fully-featured Bulma tab navigation for your Bulma React UI.
|
|
@@ -121,5 +125,7 @@ component is guaranteed to appear (the generator fails if one lacks an API page)
|
|
|
121
125
|
|
|
122
126
|
- [ConfigProvider](https://bestax.io/docs/api/helpers/config) — The `ConfigProvider` component provides a React context for configuring global settings across all Bulma UI components.
|
|
123
127
|
- [Theme](https://bestax.io/docs/api/helpers/theme) — The `Theme` component provides a powerful way to customize Bulma's appearance using CSS custom properties (CSS variables).
|
|
128
|
+
- [Valid value constants](https://bestax.io/docs/api/helpers/valid-values) — The `valid*` constant arrays enumerate every accepted value for the shared Bulma helper props — public API you can import to build prop types and validation.
|
|
124
129
|
- [classNames](https://bestax.io/docs/api/helpers/classnames) — `classNames` is a utility function for conditionally joining class names together.
|
|
125
130
|
- [useBulmaClasses](https://bestax.io/docs/api/helpers/usebulmaclasses) — `useBulmaClasses` is a custom React hook that generates Bulma helper class strings from a set of props.
|
|
131
|
+
- [usePrefixedClassNames](https://bestax.io/docs/api/helpers/useprefixedclassnames) — `usePrefixedClassNames` builds a component class string that honors the `classPrefix` from `ConfigProvider` — the hook every bestax component uses for its own…
|