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.
@@ -1,21 +1,21 @@
1
1
  ---
2
2
  name: bestax-custom-component
3
- description: Build a new custom Bulma "extra" component for @allxsmith/bestax-bulma — a React + TypeScript component with Bulma v1 SCSS (the CSS-variable pattern), Storybook stories, tests, and docs. Use when adding a component that goes beyond stock Bulma (like Dialog, Carousel, Switch, Slider, Rate, Taginput), or when extending an existing one to match the library's conventions.
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-bulma component
7
+ # Building a custom component the bestax way
8
8
 
9
- This skill teaches the end-to-end pattern the library uses for its custom "extra"
10
- components the ones that aren't part of stock Bulma. Follow it whenever you add a new
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
- ## Use when
12
+ ## Which context are you in?
14
13
 
15
- - Creating a new component beyond stock Bulma (an interactive widget, a composed element).
16
- - Writing the component's SCSS and you need it to follow the **Bulma v1 CSS-variable pattern**
17
- (`register-vars` / `getVar`, `--bulma-*` custom properties, the class prefix).
18
- - Wiring a component into the package exports, the SCSS bundle, Storybook, tests, and docs.
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 (generated from the API docs). Scan it for the name and its
34
- synonyms before anything else.
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 or extending** the existing pieces inside your new
45
- component rather than re-implementing them. (E.g. a "profile card" → there's no `ProfileCard`,
46
- but `Card`, `Image`, `Title`, `SubTitle`, and `Content` exist; build `ProfileCard` to compose
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
- ## File layout
49
+ ## Composition first
55
50
 
56
- Every custom component has five files. Mirror the existing names exactly (PascalCase TSX,
57
- `_kebab.scss` partial):
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
- ## Component template
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, { forwardRef } from 'react';
77
- import { classNames, usePrefixedClassNames } from '../helpers/classNames';
78
- import { useBulmaClasses, BulmaClassesProps } from '../helpers/useBulmaClasses';
79
-
80
- export type MyComponentColor =
81
- 'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger';
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?: MyComponentColor;
95
- size?: 'small' | 'medium' | 'large';
96
- isActive?: boolean;
74
+ color?: 'primary' | 'link' | 'info' | 'success' | 'warning' | 'danger';
97
75
  }
98
76
 
99
- /**
100
- * MyComponent — short description of what it does.
101
- *
102
- * @example
103
- * <MyComponent color="primary" size="large" isActive>Hello</MyComponent>
104
- */
105
- export const MyComponent = forwardRef<HTMLDivElement, MyComponentProps>(
106
- ({ color, size, isActive, className, children, ...props }, ref) => {
107
- // 1. Pull Bulma helper classes (m/p, text*, display, etc.) out of props.
108
- const { bulmaHelperClasses, rest } = useBulmaClasses(props);
109
-
110
- // 2. Build this component's own classes (respects the ConfigProvider classPrefix).
111
- const mainClasses = usePrefixedClassNames('mycomponent', {
112
- [`is-${color}`]: !!color,
113
- [`is-${size}`]: !!size,
114
- 'is-active': !!isActive,
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
- // 3. Consume via cv.getVar. Prefix every selector with iv.$class-prefix.
184
- .#{iv.$class-prefix}mycomponent {
185
- background-color: cv.getVar('mycomponent-background');
186
- border-radius: cv.getVar('mycomponent-radius');
187
- color: cv.getVar('mycomponent-color');
188
- padding: cv.getVar('mycomponent-padding');
189
- }
190
-
191
- // Color variants reuse Bulma's registered color vars.
192
- .#{iv.$class-prefix}mycomponent.#{iv.$class-prefix}is-primary {
193
- background-color: cv.getVar('primary');
194
- color: cv.getVar('primary-invert');
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
- // Respect reduced-motion if you animate.
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
- Why this matters: registering vars makes the component themeable at runtime (the docs site and
206
- `Theme`/`ConfigProvider` providers override `--bulma-*` properties), and the `iv.$class-prefix` keeps
207
- the component working when consumers opt into a class prefix to avoid collisions.
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
- ### Default
320
-
321
- ```tsx live
322
- <MyComponent>Hello</MyComponent>
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
- ## Visually inspect it in a browser
137
+ ## Verify in the browser
355
138
 
356
- Types and unit tests don't see layout. **Render the component and actually look at it** before
357
- you call it done spacing, padding, vertical centering, alignment, and every variant/state
358
- (colors, sizes, hover/active, dark mode). Visual bugs hide from `tsc` and `@testing-library`.
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
- 1. Run a surface that renders it: `pnpm storybook` (compiles SCSS live) or the docs dev server.
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
- Fix what you see, then re-inspect. A green test suite with a misaligned component is not done.
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
- - [ ] **Checked the inventory first** — searched `src/index.ts` / docs / Storybook for an existing
376
- match or synonym, and told the user (reuse/extend it, or confirm there's a genuine gap).
377
- - [ ] `MyComponent.tsx``forwardRef`, `Omit<…, 'color'>`, `useBulmaClasses`,
378
- `usePrefixedClassNames`, `classNames`, spread `rest`, `displayName` set.
379
- - [ ] `_mycomponent.scss` `@use` Bulma utilities, `$vars !default`, `cv.register-vars`,
380
- `cv.getVar`, every selector prefixed with `iv.$class-prefix`.
381
- - [ ] `MyComponent.stories.tsx` `tags: ['autodocs']`, `argTypes`, one story per variant.
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
- The shared helpers live in `bulma-ui/src/helpers/`. Import them from there in components.
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
- 81 documented components. Generated from the API docs — every exported
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…